From a95d5e83b4d0ab3a9bf2adef1ae4246adc317d04 Mon Sep 17 00:00:00 2001 From: Arnav Rawat Date: Thu, 11 Mar 2021 16:09:55 -0600 Subject: Support for pinned messages Fixes issue #188 --- lib/events/simplestateevents.h | 17 +++++++++++++++++ lib/room.cpp | 14 ++++++++++++++ lib/room.h | 2 ++ libQuotient.kdev4 | 4 ++++ 4 files changed, 37 insertions(+) create mode 100644 libQuotient.kdev4 diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index d6261a8f..f22f313d 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -71,4 +71,21 @@ public: QStringList aliases() const { return content().value; } }; REGISTER_EVENT_TYPE(RoomAliasesEvent) + +class RoomPinnedEvent + : public StateEvent> +{ +public: + DEFINE_EVENT_TYPEID("m.room.pinned_messages", RoomPinnedEvent) + + explicit RoomPinnedEvent(const QJsonObject& json) + : StateEvent(typeId(), json, QStringLiteral("pinned")) + { } + explicit RoomPinnedEvent(const QStringList& roomEvents) + : StateEvent(typeId(), matrixTypeId(), {}, + QStringLiteral("pinned"), roomEvents) + { } + QStringList pinnedEvents() const { return content().value; } +}; +REGISTER_EVENT_TYPE(RoomPinnedEvent) } // namespace Quotient diff --git a/lib/room.cpp b/lib/room.cpp index fadcea17..4f7f7ca9 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -557,6 +557,16 @@ QString Room::canonicalAlias() const QString Room::displayName() const { return d->displayname; } +QList Room::pinnedEvents() const +{ + QStringList events = d->getCurrentState()->pinnedEvents(); + QList pinnedEvents; + QStringList::iterator i; + for (i = events.begin(); i != events.end(); ++i) + pinnedEvents.append(findInTimeline(*i)->event()); + return pinnedEvents; +} + void Room::refreshDisplayName() { d->updateDisplayname(); } QString Room::topic() const @@ -1830,6 +1840,10 @@ void Room::setCanonicalAlias(const QString& newAlias) d->requestSetState(newAlias, altAliases()); } +void Room::setPinnedMessages(const QStringList& events) +{ + d->requestSetState(events); +} void Room::setLocalAliases(const QStringList& aliases) { d->requestSetState(canonicalAlias(), aliases); diff --git a/lib/room.h b/lib/room.h index a8275ce9..60eadacc 100644 --- a/lib/room.h +++ b/lib/room.h @@ -191,6 +191,7 @@ public: QStringList altAliases() const; QStringList aliases() const; QString displayName() const; + QList pinnedEvents() const; QString topic() const; QString avatarMediaId() const; QUrl avatarUrl() const; @@ -566,6 +567,7 @@ public Q_SLOTS: SetRoomStateWithKeyJob* setState(const StateEventBase& evt) const; void setName(const QString& newName); void setCanonicalAlias(const QString& newAlias); + void setPinnedMessages(const QStringList& events); /// Set room aliases on the user's current server void setLocalAliases(const QStringList& aliases); void setTopic(const QString& newTopic); diff --git a/libQuotient.kdev4 b/libQuotient.kdev4 new file mode 100644 index 00000000..2afdb72d --- /dev/null +++ b/libQuotient.kdev4 @@ -0,0 +1,4 @@ +[Project] +CreatedFrom=CMakeLists.txt +Manager=KDevCMakeManager +Name=libQuotient -- cgit v1.2.3 From 847bfdca018ce944360c47c18ffee8a4c3d20f5b Mon Sep 17 00:00:00 2001 From: Arnav Rawat Date: Thu, 11 Mar 2021 16:19:25 -0600 Subject: Whoops --- libQuotient.kdev4 | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 libQuotient.kdev4 diff --git a/libQuotient.kdev4 b/libQuotient.kdev4 deleted file mode 100644 index 2afdb72d..00000000 --- a/libQuotient.kdev4 +++ /dev/null @@ -1,4 +0,0 @@ -[Project] -CreatedFrom=CMakeLists.txt -Manager=KDevCMakeManager -Name=libQuotient -- cgit v1.2.3 From 5155f825bb71fca649a97ac4a7fa356cf85ee722 Mon Sep 17 00:00:00 2001 From: Arnav Rawat Date: Thu, 11 Mar 2021 18:33:54 -0600 Subject: historyedge() a result of findInTimeline() --- lib/room.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index 4f7f7ca9..ed07868b 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -562,8 +562,11 @@ QList Room::pinnedEvents() const QStringList events = d->getCurrentState()->pinnedEvents(); QList pinnedEvents; QStringList::iterator i; - for (i = events.begin(); i != events.end(); ++i) - pinnedEvents.append(findInTimeline(*i)->event()); + for (i = events.begin(); i != events.end(); ++i) { + auto timelineItem = findInTimeline(*i); + if (timelineItem != historyEdge()) + pinnedEvents.append(timelineItem->event()); + } return pinnedEvents; } -- cgit v1.2.3 From 17bf4d180297c7e87363e179b8afa79ddb15dca7 Mon Sep 17 00:00:00 2001 From: Arnav Rawat Date: Tue, 25 May 2021 14:01:51 -0500 Subject: Fixes --- lib/events/simplestateevents.h | 18 +----------------- lib/room.cpp | 16 ++++++++++++---- lib/room.h | 9 +++++++-- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index f22f313d..c977cb6e 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -55,6 +55,7 @@ namespace EventContent { DEFINE_SIMPLE_STATE_EVENT(RoomNameEvent, "m.room.name", QString, name) DEFINE_SIMPLE_STATE_EVENT(RoomTopicEvent, "m.room.topic", QString, topic) +DEFINE_SIMPLE_STATE_EVENT(RoomPinnedEvent, "m.room.pinned_messages", QStringList, pinnedEvents) class RoomAliasesEvent : public StateEvent> { @@ -71,21 +72,4 @@ public: QStringList aliases() const { return content().value; } }; REGISTER_EVENT_TYPE(RoomAliasesEvent) - -class RoomPinnedEvent - : public StateEvent> -{ -public: - DEFINE_EVENT_TYPEID("m.room.pinned_messages", RoomPinnedEvent) - - explicit RoomPinnedEvent(const QJsonObject& json) - : StateEvent(typeId(), json, QStringLiteral("pinned")) - { } - explicit RoomPinnedEvent(const QStringList& roomEvents) - : StateEvent(typeId(), matrixTypeId(), {}, - QStringLiteral("pinned"), roomEvents) - { } - QStringList pinnedEvents() const { return content().value; } -}; -REGISTER_EVENT_TYPE(RoomPinnedEvent) } // namespace Quotient diff --git a/lib/room.cpp b/lib/room.cpp index ed07868b..2a9cc0d8 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -557,13 +557,17 @@ QString Room::canonicalAlias() const QString Room::displayName() const { return d->displayname; } -QList Room::pinnedEvents() const +QStringList Room::pinnedEventIds() const { + return d->getCurrentState()->pinnedEvents(); +} + +QVector< const Quotient::RoomEvent* > Quotient::Room::pinnedEvents() const { QStringList events = d->getCurrentState()->pinnedEvents(); - QList pinnedEvents; + QVector pinnedEvents; QStringList::iterator i; for (i = events.begin(); i != events.end(); ++i) { - auto timelineItem = findInTimeline(*i); + auto timelineItem = findInTimeline(*i); if (timelineItem != historyEdge()) pinnedEvents.append(timelineItem->event()); } @@ -1843,7 +1847,7 @@ void Room::setCanonicalAlias(const QString& newAlias) d->requestSetState(newAlias, altAliases()); } -void Room::setPinnedMessages(const QStringList& events) +void Room::setPinnedEvents(const QStringList& events) { d->requestSetState(events); } @@ -2610,6 +2614,10 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) return AliasesChange; // clang-format off } + , [this] (const RoomPinnedEvent&) { + emit pinnedEventsChanged(); + return OtherChange; + } , [] (const RoomTopicEvent&) { return TopicChange; } diff --git a/lib/room.h b/lib/room.h index 60eadacc..23c0c846 100644 --- a/lib/room.h +++ b/lib/room.h @@ -85,6 +85,8 @@ class Room : public QObject { Q_PROPERTY(QStringList altAliases READ altAliases NOTIFY namesChanged) Q_PROPERTY(QString canonicalAlias READ canonicalAlias NOTIFY namesChanged) Q_PROPERTY(QString displayName READ displayName NOTIFY displaynameChanged) + Q_PROPERTY(QStringList pinnedEventIds READ pinnedEventIds WRITE setPinnedEvents + NOTIFY pinnedEventsChanged) Q_PROPERTY(QString topic READ topic NOTIFY topicChanged) Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false) @@ -191,7 +193,9 @@ public: QStringList altAliases() const; QStringList aliases() const; QString displayName() const; - QList pinnedEvents() const; + QStringList pinnedEventIds() const; + // Returns events available locally, use pinnedEventIds() for full list + QVector pinnedEvents() const; QString topic() const; QString avatarMediaId() const; QUrl avatarUrl() const; @@ -567,7 +571,7 @@ public Q_SLOTS: SetRoomStateWithKeyJob* setState(const StateEventBase& evt) const; void setName(const QString& newName); void setCanonicalAlias(const QString& newAlias); - void setPinnedMessages(const QStringList& events); + void setPinnedEvents(const QStringList& events); /// Set room aliases on the user's current server void setLocalAliases(const QStringList& aliases); void setTopic(const QString& newTopic); @@ -663,6 +667,7 @@ Q_SIGNALS: void namesChanged(Quotient::Room* room); void displaynameAboutToChange(Quotient::Room* room); void displaynameChanged(Quotient::Room* room, QString oldName); + void pinnedEventsChanged(); void topicChanged(); void avatarChanged(); void userAdded(Quotient::User* user); -- cgit v1.2.3 From d7f96a88710235c7c8648a7216bb5ad7d779889a Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 29 Nov 2021 10:58:07 +0100 Subject: Track room stubbed state size in logs --- lib/room.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/room.cpp b/lib/room.cpp index 6e6d7f11..4d60570d 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -217,6 +217,7 @@ public: evtKey.second)); qCDebug(STATE) << "A new stub event created for key {" << evtKey.first << evtKey.second << "}"; + qCDebug(STATE) << "Stubbed state size:" << stubbedState.size(); } evt = stubbedState[evtKey].get(); Q_ASSERT(evt); -- cgit v1.2.3 From ae0ad49f36e8ba5983839581302ed16ddbd75d5f Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 2 Dec 2021 14:04:49 +0100 Subject: visit(Event, ...) -> switchOnType() It has not much to do with the Visitor design pattern; also, std::visit() has different conventions on the order of parameters. --- lib/connection.cpp | 4 ++-- lib/events/event.h | 58 ++++++++++++++++++++++++++++++----------------------- lib/room.cpp | 9 +++++---- quotest/quotest.cpp | 4 ++-- 4 files changed, 42 insertions(+), 33 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index e65fdac4..25219def 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -659,7 +659,7 @@ void Connection::Private::consumeAccountData(Events&& accountDataEvents) // After running this loop, the account data events not saved in // accountData (see the end of the loop body) are auto-cleaned away for (auto&& eventPtr: accountDataEvents) { - visit(*eventPtr, + switchOnType(*eventPtr, [this](const DirectChatEvent& dce) { // https://github.com/quotient-im/libQuotient/wiki/Handling-direct-chat-events const auto& usersToDCs = dce.usersToDirectChats(); @@ -760,7 +760,7 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) << ee.senderId(); // encryptionManager->updateDeviceKeys(); - visit(*sessionDecryptMessage(ee), + switchOnType(*sessionDecryptMessage(ee), [this, senderKey = ee.senderKey()](const RoomKeyEvent& roomKeyEvent) { if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); diff --git a/lib/events/event.h b/lib/events/event.h index 998a386c..ce737280 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -290,7 +290,7 @@ using Events = EventsArray; } \ // End of macro -// === is<>(), eventCast<>() and visit<>() === +// === is<>(), eventCast<>() and switchOnType<>() === template inline bool is(const Event& e) @@ -312,12 +312,12 @@ inline auto eventCast(const BasePtrT& eptr) : nullptr; } -// A single generic catch-all visitor +// A trivial generic catch-all "switch" template -inline auto visit(const BaseEventT& event, FnT&& visitor) - -> decltype(visitor(event)) +inline auto switchOnType(const BaseEventT& event, FnT&& fn) + -> decltype(fn(event)) { - return visitor(event); + return fn(event); } namespace _impl { @@ -328,52 +328,60 @@ namespace _impl { && !std::is_same_v>>; } -// A single type-specific void visitor +// A trivial type-specific "switch" for a void function template -inline auto visit(const BaseT& event, FnT&& visitor) +inline auto switchOnType(const BaseT& event, FnT&& fn) -> std::enable_if_t<_impl::needs_downcast && std::is_void_v>> { using event_type = fn_arg_t; if (is>(event)) - visitor(static_cast(event)); + fn(static_cast(event)); } -// A single type-specific non-void visitor with an optional default value -// non-voidness is guarded by defaultValue type +// A trivial type-specific "switch" for non-void functions with an optional +// default value; non-voidness is guarded by defaultValue type template -inline auto visit(const BaseT& event, FnT&& visitor, - fn_return_t&& defaultValue = {}) +inline auto switchOnType(const BaseT& event, FnT&& fn, + fn_return_t&& defaultValue = {}) -> std::enable_if_t<_impl::needs_downcast, fn_return_t> { using event_type = fn_arg_t; if (is>(event)) - return visitor(static_cast(event)); - return std::forward>(defaultValue); + return fn(static_cast(event)); + return std::move(defaultValue); } -// A chain of 2 or more visitors +// A switch for a chain of 2 or more functions template -inline std::common_type_t, fn_return_t> visit( - const BaseT& event, FnT1&& visitor1, FnT2&& visitor2, - FnTs&&... visitors) +inline std::common_type_t, fn_return_t> +switchOnType(const BaseT& event, FnT1&& fn1, FnT2&& fn2, FnTs&&... fns) { using event_type1 = fn_arg_t; if (is>(event)) - return visitor1(static_cast(event)); - return visit(event, std::forward(visitor2), - std::forward(visitors)...); + return fn1(static_cast(event)); + return switchOnType(event, std::forward(fn2), + std::forward(fns)...); } -// A facility overload that calls void-returning visit() on each event +template +[[deprecated("The new name for visit() is switchOnType()")]] // +inline std::common_type_t...> +visit(const BaseT& event, FnTs&&... fns) +{ + return switchOnType(event, std::forward(fns)...); +} + + // A facility overload that calls void-returning switchOnType() on each event // over a range of event pointers +// TODO: replace with ranges::for_each once all standard libraries have it template -inline auto visitEach(RangeT&& events, FnTs&&... visitors) +inline auto visitEach(RangeT&& events, FnTs&&... fns) -> std::enable_if_t(visitors)...))>> + decltype(switchOnType(**begin(events), std::forward(fns)...))>> { for (auto&& evtPtr: events) - visit(*evtPtr, std::forward(visitors)...); + switchOnType(*evtPtr, std::forward(fns)...); } } // namespace Quotient Q_DECLARE_METATYPE(Quotient::Event*) diff --git a/lib/room.cpp b/lib/room.cpp index 4d60570d..b3438e08 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2779,7 +2779,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) auto& curStateEvent = d->currentState[{ e.matrixType(), e.stateKey() }]; // Prepare for the state change // clang-format off - const bool proceed = visit(e + const bool proceed = switchOnType(e , [this, curStateEvent](const RoomMemberEvent& rme) { // clang-format on auto* oldRme = static_cast(curStateEvent); @@ -2877,7 +2877,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) // Update internal structures as per the change and work out the return value // clang-format off - const auto result = visit(e + const auto result = switchOnType(e , [] (const RoomNameEvent&) { return Change::Name; } @@ -2885,7 +2885,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) // clang-format on setObjectName(cae.alias().isEmpty() ? d->id : cae.alias()); const auto* oldCae = - static_cast(oldStateEvent); + static_cast(oldStateEvent); QStringList previousAltAliases {}; if (oldCae) { previousAltAliases = oldCae->altAliases(); @@ -2897,7 +2897,8 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) if (!cae.alias().isEmpty()) newAliases.push_front(cae.alias()); - connection()->updateRoomAliases(id(), previousAltAliases, newAliases); + connection()->updateRoomAliases(id(), previousAltAliases, + newAliases); return Change::Aliases; // clang-format off } diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index d006c7fb..764d5dfd 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -498,8 +498,8 @@ bool TestSuite::checkFileSendingOutcome(const TestToken& thisTest, clog << "File event " << txnId.toStdString() << " arrived in the timeline" << endl; - // This part tests visit() - return visit( + // This part tests switchOnType() + return switchOnType( *evt, [&](const RoomMessageEvent& e) { // TODO: check #366 once #368 is implemented -- cgit v1.2.3 From dc08fb9dfd474023084de9ce86f29f177ca52fdc Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 2 Dec 2021 15:24:44 +0100 Subject: Improve function_traits<>; split out from util.* Quotient::function_traits<> did not support member functions in a proper way (i.e. the way std::invoke_result<> treats them, with the function's owning class represented as the first parameter). Now that I gained the skill and understanding in function_traits<> somewhat wicked machinery, I could properly support member functions. Overloads and generic lambdas are not supported but maybe we'll get to those one day. --- CMakeLists.txt | 2 ++ lib/events/event.h | 1 + lib/function_traits.cpp | 53 +++++++++++++++++++++++++++ lib/function_traits.h | 93 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/qt_connection_util.h | 2 +- lib/util.cpp | 32 ----------------- lib/util.h | 52 --------------------------- 7 files changed, 150 insertions(+), 85 deletions(-) create mode 100644 lib/function_traits.cpp create mode 100644 lib/function_traits.h diff --git a/CMakeLists.txt b/CMakeLists.txt index aa3b9c98..ca92699c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -125,6 +125,8 @@ endif () # Set up source files list(APPEND lib_SRCS lib/quotient_common.h + lib/function_traits.h + lib/function_traits.cpp lib/networkaccessmanager.cpp lib/connectiondata.cpp lib/connection.cpp diff --git a/lib/events/event.h b/lib/events/event.h index ce737280..4d4bb16b 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -5,6 +5,7 @@ #include "converters.h" #include "logging.h" +#include "function_traits.h" namespace Quotient { // === event_ptr_tt<> and type casting facilities === diff --git a/lib/function_traits.cpp b/lib/function_traits.cpp new file mode 100644 index 00000000..4ff427e4 --- /dev/null +++ b/lib/function_traits.cpp @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "function_traits.h" + +// Tests for function_traits<> + +using namespace Quotient; + +int f_(); +static_assert(std::is_same, int>::value, + "Test fn_return_t<>"); + +void f1_(int, float); +static_assert(std::is_same, float>::value, + "Test fn_arg_t<>"); + +struct Fo { + int operator()(); + static constexpr auto l = [] { return 0.0f; }; + bool memFn(); + void constMemFn() const&; + double field; + const double field2; +}; +static_assert(std::is_same_v, int>, + "Test return type of function object"); +static_assert(std::is_same_v, float>, + "Test return type of lambda"); +static_assert(std::is_same_v, Fo>, + "Test first argument type of member function"); +static_assert(std::is_same_v, bool>, + "Test return type of member function"); +static_assert(std::is_same_v, const Fo&>, + "Test first argument type of const member function"); +static_assert(std::is_void_v>, + "Test return type of const member function"); +static_assert(std::is_same_v, double&>, + "Test return type of a class member"); +static_assert(std::is_same_v, const double&>, + "Test return type of a const class member"); + +struct Fo1 { + void operator()(int); +}; +static_assert(std::is_same, int>(), + "Test fn_arg_t defaulting to first argument"); + +template +static void ft(const std::vector&); +static_assert( + std::is_same)>, const std::vector&>(), + "Test function templates"); diff --git a/lib/function_traits.h b/lib/function_traits.h new file mode 100644 index 00000000..83b8e425 --- /dev/null +++ b/lib/function_traits.h @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: 2018 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include + +namespace Quotient { + +namespace _impl { + template + struct fn_traits {}; +} + +/// Determine traits of an arbitrary function/lambda/functor +/*! + * Doesn't work with generic lambdas and function objects that have + * operator() overloaded. + * \sa + * https://stackoverflow.com/questions/7943525/is-it-possible-to-figure-out-the-parameter-type-and-return-type-of-a-lambda#7943765 + */ +template +struct function_traits + : public _impl::fn_traits> {}; + +// Specialisation for a function +template +struct function_traits { + using return_type = ReturnT; + using arg_types = std::tuple; + // See also the comment for wrap_in_function() in qt_connection_util.h + using function_type = std::function; +}; + +namespace _impl { + template + struct fn_object_traits; + + // Specialisation for a lambda function + template + struct fn_object_traits + : function_traits {}; + + // Specialisation for a const lambda function + template + struct fn_object_traits + : function_traits {}; + + // Specialisation for function objects with (non-overloaded) operator() + // (this includes non-generic lambdas) + template + struct fn_traits + : public fn_object_traits {}; + + // Specialisation for a member function in a non-functor class + template + struct fn_traits + : function_traits {}; + + // Specialisation for a const member function + template + struct fn_traits + : function_traits {}; + + // Specialisation for a constref member function + template + struct fn_traits + : function_traits {}; + + // Specialisation for a prvalue member function + template + struct fn_traits + : function_traits {}; + + // Specialisation for a pointer-to-member + template + struct fn_traits + : function_traits {}; + + // Specialisation for a const pointer-to-member + template + struct fn_traits + : function_traits {}; +} // namespace _impl + +template +using fn_return_t = typename function_traits::return_type; + +template +using fn_arg_t = + std::tuple_element_t::arg_types>; + +} // namespace Quotient diff --git a/lib/qt_connection_util.h b/lib/qt_connection_util.h index 46294499..86593cc8 100644 --- a/lib/qt_connection_util.h +++ b/lib/qt_connection_util.h @@ -3,7 +3,7 @@ #pragma once -#include "util.h" +#include "function_traits.h" #include diff --git a/lib/util.cpp b/lib/util.cpp index 2dfb09a6..03ebf325 100644 --- a/lib/util.cpp +++ b/lib/util.cpp @@ -135,35 +135,3 @@ int Quotient::patchVersion() { return Quotient_VERSION_PATCH; } - -// Tests for function_traits<> - -using namespace Quotient; - -int f_(); -static_assert(std::is_same, int>::value, - "Test fn_return_t<>"); - -void f1_(int, QString); -static_assert(std::is_same, QString>::value, - "Test fn_arg_t<>"); - -struct Fo { - int operator()(); - static constexpr auto l = [] { return 0.0f; }; -}; -static_assert(std::is_same, int>::value, - "Test return type of function object"); -static_assert(std::is_same, float>::value, - "Test return type of lambda"); - -struct Fo1 { - void operator()(int); -}; -static_assert(std::is_same, int>(), - "Test fn_arg_t defaulting to first argument"); - -template -static QString ft(T&&); -static_assert(std::is_same)>, QString&&>(), - "Test function templates"); diff --git a/lib/util.h b/lib/util.h index 13efb94b..97f0ecbc 100644 --- a/lib/util.h +++ b/lib/util.h @@ -189,58 +189,6 @@ inline auto merge(T1& lhs, const Omittable& rhs) return true; } -namespace _impl { - template - struct fn_traits {}; -} - -/// Determine traits of an arbitrary function/lambda/functor -/*! - * Doesn't work with generic lambdas and function objects that have - * operator() overloaded. - * \sa - * https://stackoverflow.com/questions/7943525/is-it-possible-to-figure-out-the-parameter-type-and-return-type-of-a-lambda#7943765 - */ -template -struct function_traits - : public _impl::fn_traits> {}; - -// Specialisation for a function -template -struct function_traits { - using return_type = ReturnT; - using arg_types = std::tuple; - // Doesn't (and there's no plan to make it) work for "classic" - // member functions (i.e. outside of functors). - // See also the comment for wrap_in_function() below - using function_type = std::function; -}; - -namespace _impl { - // Specialisation for function objects with (non-overloaded) operator() - // (this includes non-generic lambdas) - template - struct fn_traits - : public fn_traits {}; - - // Specialisation for a member function - template - struct fn_traits - : function_traits {}; - - // Specialisation for a const member function - template - struct fn_traits - : function_traits {}; -} // namespace _impl - -template -using fn_return_t = typename function_traits::return_type; - -template -using fn_arg_t = - std::tuple_element_t::arg_types>; - inline constexpr auto operator"" _ls(const char* s, std::size_t size) { return QLatin1String(s, int(size)); -- cgit v1.2.3 From c3a0515ac2ead7b2d0653be3517836cd31be480e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 2 Dec 2021 21:18:30 +0100 Subject: Cleanup on Sonar issues --- lib/converters.h | 4 ++-- lib/function_traits.cpp | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/converters.h b/lib/converters.h index 90349019..9c3d5749 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -27,8 +27,8 @@ struct JsonObjectConverter { //! \brief The switchboard for extra conversion algorithms behind from/toJson //! //! This template is mainly intended for partial conversion specialisations -//! since from/toJson are functions and cannot be partially specialised; -//! another case for JsonConverter is to insulate types that can be constructed +//! since from/toJson are functions and cannot be partially specialised. +//! Another case for JsonConverter is to insulate types that can be constructed //! from basic types - namely, QVariant and QUrl can be directly constructed //! from QString and having an overload or specialisation for those leads to //! ambiguity between these and QJsonValue. For trivial (converting diff --git a/lib/function_traits.cpp b/lib/function_traits.cpp index 4ff427e4..20bcf30e 100644 --- a/lib/function_traits.cpp +++ b/lib/function_traits.cpp @@ -8,11 +8,11 @@ using namespace Quotient; int f_(); -static_assert(std::is_same, int>::value, +static_assert(std::is_same_v, int>, "Test fn_return_t<>"); void f1_(int, float); -static_assert(std::is_same, float>::value, +static_assert(std::is_same_v, float>, "Test fn_arg_t<>"); struct Fo { @@ -43,7 +43,7 @@ static_assert(std::is_same_v, const double&>, struct Fo1 { void operator()(int); }; -static_assert(std::is_same, int>(), +static_assert(std::is_same_v, int>, "Test fn_arg_t defaulting to first argument"); template -- cgit v1.2.3 From 3b5d5f65c5eb10997362f9f9509fe2161eaa335c Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 3 Dec 2021 15:07:22 +0100 Subject: CMakeLists.txt: add .h files to sources This is driven by the change in the way Qt Creator 6 works with CMake (see https://www.qt.io/blog/qt-creator-6-cmake-update) that basically requires you to explicitly add header files as source files. While this obviously added to the size of the source files list, it also drove dropping the repeated file(GLOB_RECURSE ... CONFIGURE_DEPENDS) call which added a considerable speedbump to the beginning of each build (now that call is just one). --- CMakeLists.txt | 131 ++++++++++++++++++++++++++------------------------------- 1 file changed, 60 insertions(+), 71 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ca92699c..d1dcc106 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -125,54 +125,53 @@ endif () # Set up source files list(APPEND lib_SRCS lib/quotient_common.h - lib/function_traits.h - lib/function_traits.cpp - lib/networkaccessmanager.cpp - lib/connectiondata.cpp - lib/connection.cpp - lib/ssosession.cpp - lib/logging.cpp - lib/room.cpp - lib/user.cpp - lib/avatar.cpp - lib/uri.cpp - lib/uriresolver.cpp - lib/eventstats.cpp - lib/syncdata.cpp - lib/settings.cpp - lib/networksettings.cpp - lib/converters.cpp - lib/util.cpp - lib/encryptionmanager.cpp - lib/eventitem.cpp - lib/accountregistry.cpp - lib/mxcreply.cpp - lib/events/event.cpp - 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/roompowerlevelsevent.cpp - lib/events/typingevent.cpp - lib/events/receiptevent.cpp - lib/events/reactionevent.cpp - lib/events/callanswerevent.cpp - lib/events/callcandidatesevent.cpp - lib/events/callhangupevent.cpp - lib/events/callinviteevent.cpp - lib/events/directchatevent.cpp - lib/events/encryptionevent.cpp - lib/events/encryptedevent.cpp - lib/events/roomkeyevent.cpp - lib/events/stickerevent.cpp - lib/jobs/requestdata.cpp - lib/jobs/basejob.cpp - lib/jobs/syncjob.cpp - lib/jobs/mediathumbnailjob.cpp - lib/jobs/downloadfilejob.cpp + lib/function_traits.h lib/function_traits.cpp + 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/user.h lib/user.cpp + lib/avatar.h lib/avatar.cpp + lib/uri.h lib/uri.cpp + lib/uriresolver.h lib/uriresolver.cpp + lib/eventstats.h lib/eventstats.cpp + lib/syncdata.h lib/syncdata.cpp + lib/settings.h lib/settings.cpp + lib/networksettings.h lib/networksettings.cpp + lib/converters.h lib/converters.cpp + lib/util.h lib/util.cpp + lib/encryptionmanager.h lib/encryptionmanager.cpp + lib/eventitem.h lib/eventitem.cpp + lib/accountregistry.h lib/accountregistry.cpp + lib/mxcreply.h lib/mxcreply.cpp + lib/events/event.h lib/events/event.cpp + lib/events/roomevent.h lib/events/roomevent.cpp + lib/events/stateevent.h lib/events/stateevent.cpp + lib/events/eventcontent.h lib/events/eventcontent.cpp + lib/events/roomcreateevent.h lib/events/roomcreateevent.cpp + lib/events/roomtombstoneevent.h lib/events/roomtombstoneevent.cpp + lib/events/roommessageevent.h lib/events/roommessageevent.cpp + lib/events/roommemberevent.h lib/events/roommemberevent.cpp + lib/events/roompowerlevelsevent.h lib/events/roompowerlevelsevent.cpp + lib/events/typingevent.h lib/events/typingevent.cpp + lib/events/receiptevent.h lib/events/receiptevent.cpp + lib/events/reactionevent.h lib/events/reactionevent.cpp + lib/events/callanswerevent.h lib/events/callanswerevent.cpp + lib/events/callcandidatesevent.h lib/events/callcandidatesevent.cpp + lib/events/callhangupevent.h lib/events/callhangupevent.cpp + lib/events/callinviteevent.h lib/events/callinviteevent.cpp + lib/events/directchatevent.h lib/events/directchatevent.cpp + lib/events/encryptionevent.h lib/events/encryptionevent.cpp + lib/events/encryptedevent.h lib/events/encryptedevent.cpp + lib/events/roomkeyevent.h lib/events/roomkeyevent.cpp + lib/events/stickerevent.h lib/events/stickerevent.cpp + lib/jobs/requestdata.h lib/jobs/requestdata.cpp + lib/jobs/basejob.h lib/jobs/basejob.cpp + lib/jobs/syncjob.h lib/jobs/syncjob.cpp + lib/jobs/mediathumbnailjob.h lib/jobs/mediathumbnailjob.cpp + lib/jobs/downloadfilejob.h lib/jobs/downloadfilejob.cpp ) # Configure API files generation @@ -217,21 +216,13 @@ if (API_GENERATION_ENABLED) message( STATUS "${CLANG_FORMAT} is NOT FOUND; API files won't be reformatted") endif () - # We use globbing with CONFIGURE_DEPENDS to produce two file lists: - # one of all API files for clang-format and another of just .cpp - # files to supply for library source files. Since we expect these - # file lists to only change due to GTAD invocation, we only use - # CONFIGURE_DEPENDS when pre-requisites to update API are met. - # Read comments next to each file(GLOB_RECURSE) for caveats. - set(add_CONFIGURE_DEPENDS "CONFIGURE_DEPENDS") - set(FULL_CSAPI_SRC_DIR ${ABS_API_DEF_PATH}/client-server) file(GLOB_RECURSE API_DEFS RELATIVE ${PROJECT_SOURCE_DIR} ${FULL_CSAPI_SRC_DIR}/*.yaml ${ABS_API_DEF_PATH}/${ASAPI_DEF_DIR}/*.yaml ${ABS_API_DEF_PATH}/${ISAPI_DEF_DIR}/*.yaml ) - add_custom_target(generate-unformatted-api + add_custom_target(update-api ${ABS_GTAD_PATH} --config ../gtad/gtad.yaml --out ${CSAPI_DIR} ${FULL_CSAPI_SRC_DIR} old_sync.yaml- room_initial_sync.yaml- # deprecated @@ -271,21 +262,19 @@ if (API_GENERATION_ENABLED) endif() add_feature_info(EnableApiCodeGeneration "${API_GENERATION_ENABLED}" "build target update-api") -if (API_GENERATION_ENABLED) - add_feature_info(EnableApiFormatting "${API_FORMATTING_ENABLED}" - "formatting of generated API files with clang-format") -endif() - -# Make no mistake: CMake cannot run gtad first and then populate the list of -# resulting api_SRCS files. In other words, placing the below statement after -# the block above will not lead to CMake magically reconfiguring the build -# after building the update-api target. CONFIGURE_DEPENDS somewhat helps, -# at least forcing the build system to reevaluate the glob before building -# the next target. Otherwise, you have to watch out if gtad has created -# new files and if it has, re-run cmake. -file(GLOB_RECURSE api_SRCS ${add_CONFIGURE_DEPENDS} ${FULL_CSAPI_DIR}/*.cpp) -add_library(${PROJECT_NAME} ${lib_SRCS} ${api_SRCS}) +# Produce the list of all Matrix API files for building the library. When this +# list changes (normally after calling GTAD), CONFIGURE_DEPENDS will force +# the build system to call CMake again. Checking for the glob change slows down +# each build (even if the target does not involve API generation). It would be +# ideal if GTAD could compare the initial (saved somewhere) and the generated +# file list itself and write down to some .cmake file if those are different, +# which would trigger the reconfiguration specifically before the next build. +# For now CONFIGURE_DEPENDS is the best approximation of that. +file(GLOB_RECURSE api_ALL_SRCS CONFIGURE_DEPENDS + ${FULL_CSAPI_DIR}/*.* lib/${ASAPI_DEF_DIR}/*.* lib/${ISAPI_DEF_DIR}/*.*) + +add_library(${PROJECT_NAME} ${lib_SRCS} ${api_ALL_SRCS}) target_compile_definitions(${PROJECT_NAME} PRIVATE QT_NO_JAVA_STYLE_ITERATORS QT_NO_URL_CAST_FROM_STRING QT_NO_CAST_TO_ASCII) target_compile_definitions(${PROJECT_NAME} PUBLIC ${PROJECT_NAME}_VERSION_MAJOR=${PROJECT_VERSION_MAJOR} -- cgit v1.2.3 From dc458f3952b0005a0feb63fbebed11ae9ad5a062 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 3 Dec 2021 15:11:32 +0100 Subject: Rely on GTAD 0.9 that calls clang-format itself There was that ugly workaround in CMakeLists.txt to produce the list of files to be formatted and making a separate build target for clang-format. As GTAD 0.9 calls clang-format itself this workaround is no more necessary; generate-unformatted-api and format-api build targets are now gone, and clang-format is no more one-build-system-configuration behind GTAD on the list of files to handle. --- CMakeLists.txt | 30 ++---------------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d1dcc106..8d9be635 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -182,7 +182,6 @@ set(ASAPI_DEF_DIR application-service/definitions) set(ISAPI_DEF_DIR identity/definitions) set(API_GENERATION_ENABLED 0) -set(API_FORMATTING_ENABLED 0) if (GTAD_PATH AND MATRIX_DOC_PATH) # REALPATH resolves ~ (home directory) while PROGRAM doesn't get_filename_component(ABS_GTAD_PATH "${GTAD_PATH}" REALPATH) @@ -209,11 +208,8 @@ if (API_GENERATION_ENABLED) set(CLANG_FORMAT clang-format) endif() get_filename_component(ABS_CLANG_FORMAT "${CLANG_FORMAT}" PROGRAM PROGRAM_ARGS CLANG_FORMAT_ARGS) - if (ABS_CLANG_FORMAT) - set(API_FORMATTING_ENABLED 1) - message( STATUS "clang-format is at ${ABS_CLANG_FORMAT}") - else () - message( STATUS "${CLANG_FORMAT} is NOT FOUND; API files won't be reformatted") + if (NOT ABS_CLANG_FORMAT) + message( WARNING "${CLANG_FORMAT} is NOT FOUND; API files won't be formatted") endif () set(FULL_CSAPI_SRC_DIR ${ABS_API_DEF_PATH}/client-server) @@ -237,28 +233,6 @@ if (API_GENERATION_ENABLED) ${API_DEFS} VERBATIM ) - add_custom_target(update-api DEPENDS generate-unformatted-api) - if (EXISTS ${ABS_CLANG_FORMAT}) - set(CLANG_FORMAT_ARGS -i -sort-includes ${CLANG_FORMAT_ARGS}) - # FIXME: the list of files should be produced after GTAD has run. - # For now it's produced at CMake invocation. If file() hasn't found - # any files at that moment, clang-format will be called with an empty - # list of files and choke. Taking outfiles.txt from GTAD could be - # an option but this, too, must be done during the build stage, and - # there's no crossplatform way to use the contents of a file as - # input for a build target command. - file(GLOB_RECURSE api_ALL_SRCS ${add_CONFIGURE_DEPENDS} - ${FULL_CSAPI_DIR}/*.* - lib/${ASAPI_DEF_DIR}/*.* - lib/${ISAPI_DEF_DIR}/*.*) - if (api_ALL_SRCS) - add_custom_target(format-api - ${ABS_CLANG_FORMAT} ${CLANG_FORMAT_ARGS} ${api_ALL_SRCS} - DEPENDS generate-unformatted-api - VERBATIM) - add_dependencies(update-api format-api) - endif() - endif() endif() add_feature_info(EnableApiCodeGeneration "${API_GENERATION_ENABLED}" "build target update-api") -- cgit v1.2.3 From 0d212bdb7849d12582d906a03935cb6f51767e3b Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 3 Dec 2021 21:22:46 +0100 Subject: Add a couple of bare .h files to CMakeLists.txt These were missing from the commit introducing .h files to sources, highlighting the reason why .h files should be there... --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d9be635..2762df6a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -149,13 +149,16 @@ list(APPEND lib_SRCS lib/events/event.h lib/events/event.cpp lib/events/roomevent.h lib/events/roomevent.cpp lib/events/stateevent.h lib/events/stateevent.cpp + lib/events/simplestateevents.h lib/events/eventcontent.h lib/events/eventcontent.cpp lib/events/roomcreateevent.h lib/events/roomcreateevent.cpp lib/events/roomtombstoneevent.h lib/events/roomtombstoneevent.cpp lib/events/roommessageevent.h lib/events/roommessageevent.cpp lib/events/roommemberevent.h lib/events/roommemberevent.cpp + lib/events/roomavatarevent.h lib/events/roompowerlevelsevent.h lib/events/roompowerlevelsevent.cpp lib/events/typingevent.h lib/events/typingevent.cpp + lib/events/accountdataevents.h lib/events/receiptevent.h lib/events/receiptevent.cpp lib/events/reactionevent.h lib/events/reactionevent.cpp lib/events/callanswerevent.h lib/events/callanswerevent.cpp -- cgit v1.2.3 From 776d05bf98a5dd9e484d5a0e651c71fa95498689 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 3 Dec 2021 21:36:32 +0100 Subject: Cleanup; drop an unused RoomAliasesEvent constructor Also, RoomAliasesEvent is to be completely gone after 0.7. --- lib/events/accountdataevents.h | 5 ++--- lib/events/simplestateevents.h | 12 ++---------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h index e5101d20..9cf77be3 100644 --- a/lib/events/accountdataevents.h +++ b/lib/events/accountdataevents.h @@ -4,7 +4,6 @@ #pragma once #include "event.h" -#include "eventcontent.h" namespace Quotient { constexpr const char* FavouriteTag = "m.favourite"; @@ -16,12 +15,12 @@ struct TagRecord { order_type order; - TagRecord(order_type order = none) : order(std::move(order)) {} + TagRecord(order_type order = none) : order(order) {} bool operator<(const TagRecord& other) const { // Per The Spec, rooms with no order should be after those with order, - // against optional<>::operator<() convention. + // against std::optional<>::operator<() convention. return order && (!other.order || *order < *other.order); } }; diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index cf1bfbba..9ce78609 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -8,8 +8,7 @@ namespace Quotient { namespace EventContent { template - class SimpleContent { - public: + struct SimpleContent { using value_type = T; // The constructor is templated to enable perfect forwarding @@ -25,11 +24,8 @@ namespace EventContent { return { { key, Quotient::toJson(value) } }; } - public: T value; - - protected: - QString key; + const QString key; }; } // namespace EventContent @@ -65,10 +61,6 @@ public: explicit RoomAliasesEvent(const QJsonObject& obj) : StateEvent(typeId(), obj, QStringLiteral("aliases")) {} - RoomAliasesEvent(const QString& server, const QStringList& aliases) - : StateEvent(typeId(), matrixTypeId(), server, - QStringLiteral("aliases"), aliases) - {} QString server() const { return stateKey(); } QStringList aliases() const { return content().value; } }; -- cgit v1.2.3 From 7981c654c0cd53f6e100ecae25882246a9de5d6e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 5 Dec 2021 21:47:25 +0100 Subject: Drop 'qmc-example' from one last(?) place --- quotest/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quotest/CMakeLists.txt b/quotest/CMakeLists.txt index 59334e30..8d67b6f1 100644 --- a/quotest/CMakeLists.txt +++ b/quotest/CMakeLists.txt @@ -8,7 +8,7 @@ find_package(${Qt} COMPONENTS Concurrent) add_executable(quotest ${quotest_SRCS}) target_link_libraries(quotest PRIVATE ${Qt}::Core ${Qt}::Test ${Qt}::Concurrent ${PROJECT_NAME}) -option(${PROJECT_NAME}_INSTALL_TESTS "install quotest (former qmc-example) application" ON) +option(${PROJECT_NAME}_INSTALL_TESTS "install quotest application" ON) add_feature_info(InstallQuotest ${PROJECT_NAME}_INSTALL_TESTS "the library functional test suite") -- cgit v1.2.3 From 9ddb72611f5575ccd5d3b68734a9d40d8ef2befc Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 6 Dec 2021 07:17:17 +0100 Subject: Add execution of autotests to CI --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 47e55421..ec6b671c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -154,7 +154,7 @@ jobs: - name: Build and install libQuotient run: | - cmake --build build --target install + cmake --build build --target all install ls ~/.local/$BIN_DIR/quotest - name: Run tests @@ -164,6 +164,8 @@ jobs: QT_LOGGING_RULES: 'quotient.main.debug=true;quotient.jobs.debug=true' QT_MESSAGE_PATTERN: '%{time h:mm:ss.zzz}|%{category}|%{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}|%{message}' run: | + cd build + ctest --output-on-failure [[ -z "$TEST_USER" ]] || $VALGRIND build/quotest/quotest "$TEST_USER" "$TEST_PWD" quotest-gha '#quotest:matrix.org' "$QUOTEST_ORIGIN" timeout-minutes: 4 # quotest is supposed to finish within 3 minutes, actually -- cgit v1.2.3 From ac0ce7da24b4e567a34863db3261f755674d8337 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 6 Dec 2021 22:42:33 +0100 Subject: autotests/: don't instantiate QApplication Those tests don't even need an event loop. --- autotests/callcandidateseventtest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autotests/callcandidateseventtest.cpp b/autotests/callcandidateseventtest.cpp index 1a69cb66..0d5a543b 100644 --- a/autotests/callcandidateseventtest.cpp +++ b/autotests/callcandidateseventtest.cpp @@ -53,5 +53,5 @@ void TestCallCandidatesEvent::fromJson() QStringLiteral("candidate:863018703 1 udp 2122260223 10.9.64.156 43670 typ host generation 0")); } -QTEST_MAIN(TestCallCandidatesEvent) +QTEST_APPLESS_MAIN(TestCallCandidatesEvent) #include "callcandidateseventtest.moc" -- cgit v1.2.3 From 449456c2c86f56e9294294e03436d3aa0ff089f5 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 7 Dec 2021 09:43:00 +0100 Subject: Fix quotest invocation; use working-directory --- .github/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ec6b671c..9eefa43f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,7 +71,7 @@ jobs: if: startsWith(matrix.os, 'ubuntu') run: | sudo apt-get -qq install ninja-build valgrind - echo "VALGRIND=valgrind --tool=memcheck --leak-check=yes --gen-suppressions=all --suppressions=quotest/.valgrind.supp" >>$GITHUB_ENV + echo "VALGRIND=valgrind --tool=memcheck --leak-check=yes --gen-suppressions=all --suppressions=${{ runner.workspace }}/quotest/.valgrind.supp" >>$GITHUB_ENV - name: Setup build environment run: | @@ -108,8 +108,8 @@ jobs: - name: Build and install olm if: matrix.e2ee + working-directory: '..' run: | - cd .. git clone https://gitlab.matrix.org/matrix-org/olm.git cmake -S olm -B olm/build $CMAKE_ARGS cmake --build olm/build --target install @@ -117,8 +117,8 @@ jobs: - name: Pull CS API and build GTAD if: matrix.update-api + working-directory: '..' run: | - cd .. git clone https://github.com/matrix-org/matrix-doc.git git clone --recursive https://github.com/KitsuneRal/gtad.git cmake -S gtad -B gtad $CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF @@ -163,10 +163,10 @@ jobs: TEST_PWD: ${{ secrets.TEST_PWD }} QT_LOGGING_RULES: 'quotient.main.debug=true;quotient.jobs.debug=true' QT_MESSAGE_PATTERN: '%{time h:mm:ss.zzz}|%{category}|%{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}|%{message}' + working-directory: build run: | - cd build ctest --output-on-failure - [[ -z "$TEST_USER" ]] || $VALGRIND build/quotest/quotest "$TEST_USER" "$TEST_PWD" quotest-gha '#quotest:matrix.org' "$QUOTEST_ORIGIN" + [[ -z "$TEST_USER" ]] || $VALGRIND quotest/quotest "$TEST_USER" "$TEST_PWD" quotest-gha '#quotest:matrix.org' "$QUOTEST_ORIGIN" timeout-minutes: 4 # quotest is supposed to finish within 3 minutes, actually - name: Perform CodeQL analysis -- cgit v1.2.3 From cb7c57953024a23fc7c20db75f3a15f81bb62bd3 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 7 Dec 2021 14:43:27 +0100 Subject: Fix valgrind invocation failure It turned out that, confusingly, ${{ runner.workspace }} refers to the directory above $GITHUB_WORKSPACE, which is why the previous commit ended up with valgrind not finding its suppressions. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9eefa43f..6fdb337a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,7 +71,7 @@ jobs: if: startsWith(matrix.os, 'ubuntu') run: | sudo apt-get -qq install ninja-build valgrind - echo "VALGRIND=valgrind --tool=memcheck --leak-check=yes --gen-suppressions=all --suppressions=${{ runner.workspace }}/quotest/.valgrind.supp" >>$GITHUB_ENV + echo "VALGRIND=valgrind --tool=memcheck --leak-check=yes --gen-suppressions=all --suppressions=$GITHUB_WORKSPACE/quotest/.valgrind.supp" >>$GITHUB_ENV - name: Setup build environment run: | -- cgit v1.2.3 From 0128b8a296b1be7e7702ec525539614d47dd7471 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 7 Dec 2021 14:43:39 +0100 Subject: CI: Put all build directories to ${{ runner.workspace }}/build --- .github/workflows/ci.yml | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6fdb337a..2e023230 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,6 +99,7 @@ jobs: echo "CMAKE_ARGS=-G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=false \ -DCMAKE_INSTALL_PREFIX=~/.local -DCMAKE_PREFIX_PATH=~/.local" >>$GITHUB_ENV cmake -E make_directory ${{ runner.workspace }}/build + echo "BUILD_PATH=${{ runner.workspace }}/build/libQuotient" >>$GITHUB_ENV - name: Setup MSVC environment uses: ilammy/msvc-dev-cmd@v1 @@ -108,23 +109,23 @@ jobs: - name: Build and install olm if: matrix.e2ee - working-directory: '..' + working-directory: ${{ runner.workspace }} run: | git clone https://gitlab.matrix.org/matrix-org/olm.git - cmake -S olm -B olm/build $CMAKE_ARGS - cmake --build olm/build --target install + cmake -S olm -B build/olm $CMAKE_ARGS + cmake --build build/olm --target install echo "QUOTEST_ORIGIN=$QUOTEST_ORIGIN with E2EE" >>$GITHUB_ENV - name: Pull CS API and build GTAD if: matrix.update-api - working-directory: '..' + working-directory: ${{ runner.workspace }} run: | git clone https://github.com/matrix-org/matrix-doc.git git clone --recursive https://github.com/KitsuneRal/gtad.git - cmake -S gtad -B gtad $CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF - cmake --build gtad - echo "CMAKE_ARGS=$CMAKE_ARGS -DMATRIX_DOC_PATH=$GITHUB_WORKSPACE/../matrix-doc \ - -DGTAD_PATH=$GITHUB_WORKSPACE/../gtad/gtad" \ + cmake -S gtad -B build/gtad $CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF + cmake --build build/gtad + echo "CMAKE_ARGS=$CMAKE_ARGS -DMATRIX_DOC_PATH=${{ runner.workspace }}/matrix-doc \ + -DGTAD_PATH=${{ runner.workspace }}/build/gtad/gtad" \ >>$GITHUB_ENV echo "QUOTEST_ORIGIN=$QUOTEST_ORIGIN with API files regeneration" >>$GITHUB_ENV @@ -146,15 +147,15 @@ jobs: BIN_DIR=bin fi echo "BIN_DIR=$BIN_DIR" >>$GITHUB_ENV - cmake -S $GITHUB_WORKSPACE -B build $CMAKE_ARGS -DQuotient_ENABLE_E2EE=${{ matrix.e2ee }} + cmake -S $GITHUB_WORKSPACE -B $BUILD_PATH $CMAKE_ARGS -DQuotient_ENABLE_E2EE=${{ matrix.e2ee }} - name: Regenerate API code if: matrix.update-api - run: cmake --build build --target update-api + run: cmake --build ../build/libQuotient --target update-api - name: Build and install libQuotient run: | - cmake --build build --target all install + cmake --build $BUILD_PATH --target all install ls ~/.local/$BIN_DIR/quotest - name: Run tests @@ -163,9 +164,9 @@ jobs: TEST_PWD: ${{ secrets.TEST_PWD }} QT_LOGGING_RULES: 'quotient.main.debug=true;quotient.jobs.debug=true' QT_MESSAGE_PATTERN: '%{time h:mm:ss.zzz}|%{category}|%{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}|%{message}' - working-directory: build + working-directory: ../build/libQuotient run: | - ctest --output-on-failure + ctest --test-dir $BUILD_PATH --output-on-failure [[ -z "$TEST_USER" ]] || $VALGRIND quotest/quotest "$TEST_USER" "$TEST_PWD" quotest-gha '#quotest:matrix.org' "$QUOTEST_ORIGIN" timeout-minutes: 4 # quotest is supposed to finish within 3 minutes, actually -- cgit v1.2.3 From bd728a91f69382a052d07582788fcecec8b0f35e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 7 Dec 2021 19:00:04 +0100 Subject: Test installed quotest This covers Quotient_INSTALL_TESTS setting. --- .github/workflows/ci.yml | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e023230..9d286da6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -98,8 +98,16 @@ jobs: echo "QUOTEST_ORIGIN=$VERSION @ ${{ runner.os }}/${{ matrix.compiler }}" >>$GITHUB_ENV echo "CMAKE_ARGS=-G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=false \ -DCMAKE_INSTALL_PREFIX=~/.local -DCMAKE_PREFIX_PATH=~/.local" >>$GITHUB_ENV + + if [[ '${{ runner.os }}' != 'Windows' ]]; then + BIN_DIR=/bin + fi + echo "BIN_DIR=$BIN_DIR" >>$GITHUB_ENV + echo "~/.local$BIN_DIR" >>$GITHUB_PATH + cmake -E make_directory ${{ runner.workspace }}/build echo "BUILD_PATH=${{ runner.workspace }}/build/libQuotient" >>$GITHUB_ENV + echo "LD_LIBRARY_PATH=$Qt5_DIR/lib:$LD_LIBRARY_PATH" >>$GITHUB_ENV - name: Setup MSVC environment uses: ilammy/msvc-dev-cmd@v1 @@ -141,13 +149,8 @@ jobs: - name: Configure libQuotient run: | - if [[ '${{ runner.os }}' == 'Windows' ]]; then - BIN_DIR=. - else - BIN_DIR=bin - fi - echo "BIN_DIR=$BIN_DIR" >>$GITHUB_ENV - cmake -S $GITHUB_WORKSPACE -B $BUILD_PATH $CMAKE_ARGS -DQuotient_ENABLE_E2EE=${{ matrix.e2ee }} + cmake -S $GITHUB_WORKSPACE -B $BUILD_PATH $CMAKE_ARGS \ + -DQuotient_ENABLE_E2EE=${{ matrix.e2ee }} -DQuotient_INSTALL_TESTS=ON - name: Regenerate API code if: matrix.update-api @@ -164,10 +167,10 @@ jobs: TEST_PWD: ${{ secrets.TEST_PWD }} QT_LOGGING_RULES: 'quotient.main.debug=true;quotient.jobs.debug=true' QT_MESSAGE_PATTERN: '%{time h:mm:ss.zzz}|%{category}|%{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}|%{message}' - working-directory: ../build/libQuotient run: | ctest --test-dir $BUILD_PATH --output-on-failure - [[ -z "$TEST_USER" ]] || $VALGRIND quotest/quotest "$TEST_USER" "$TEST_PWD" quotest-gha '#quotest:matrix.org' "$QUOTEST_ORIGIN" + [[ -z "$TEST_USER" ]] || \ + $VALGRIND quotest "$TEST_USER" "$TEST_PWD" quotest-gha '#quotest:matrix.org' "$QUOTEST_ORIGIN" timeout-minutes: 4 # quotest is supposed to finish within 3 minutes, actually - name: Perform CodeQL analysis -- cgit v1.2.3 From c585227b3724666e3cf7aab3ab53d6f5930e7218 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 9 Dec 2021 21:07:52 +0100 Subject: Fix CI failure on macOS CMAKE_INSTALL_RPATH_USE_LINK_PATH is more universal than setting LD_LIBRARY_PATH Also: drop an extra slash in the path to installed quotest. --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d286da6..c78a5981 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -97,7 +97,8 @@ jobs: fi echo "QUOTEST_ORIGIN=$VERSION @ ${{ runner.os }}/${{ matrix.compiler }}" >>$GITHUB_ENV echo "CMAKE_ARGS=-G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=false \ - -DCMAKE_INSTALL_PREFIX=~/.local -DCMAKE_PREFIX_PATH=~/.local" >>$GITHUB_ENV + -DCMAKE_INSTALL_PREFIX=~/.local -DCMAKE_PREFIX_PATH=~/.local\ + -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=ON" >>$GITHUB_ENV if [[ '${{ runner.os }}' != 'Windows' ]]; then BIN_DIR=/bin @@ -107,7 +108,6 @@ jobs: cmake -E make_directory ${{ runner.workspace }}/build echo "BUILD_PATH=${{ runner.workspace }}/build/libQuotient" >>$GITHUB_ENV - echo "LD_LIBRARY_PATH=$Qt5_DIR/lib:$LD_LIBRARY_PATH" >>$GITHUB_ENV - name: Setup MSVC environment uses: ilammy/msvc-dev-cmd@v1 @@ -159,7 +159,7 @@ jobs: - name: Build and install libQuotient run: | cmake --build $BUILD_PATH --target all install - ls ~/.local/$BIN_DIR/quotest + ls ~/.local$BIN_DIR/quotest - name: Run tests env: -- cgit v1.2.3 From e5256e0b1e4c43ce96d99d1b82ca5d98a1baded6 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 17 Dec 2021 08:07:07 +0100 Subject: RoomMemberEvent: fix an off-by-one error Also: extended quotest to cover member renames, not just user profile renames. --- lib/events/roommemberevent.cpp | 8 +++----- quotest/quotest.cpp | 35 +++++++++++++++++++++++++++-------- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp index b0bc7bcb..3141f6b5 100644 --- a/lib/events/roommemberevent.cpp +++ b/lib/events/roommemberevent.cpp @@ -48,11 +48,9 @@ void MemberEventContent::fillJson(QJsonObject* o) const { Q_ASSERT(o); if (membership != Membership::Invalid) - o->insert( - QStringLiteral("membership"), - MembershipStrings[qCountTrailingZeroBits( - std::underlying_type_t(membership)) - + 1]); + o->insert(QStringLiteral("membership"), + MembershipStrings[qCountTrailingZeroBits( + std::underlying_type_t(membership))]); if (displayName) o->insert(QStringLiteral("displayname"), *displayName); if (avatarUrl && avatarUrl->isValid()) diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index 764d5dfd..8703efb2 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -214,6 +214,7 @@ TestManager::TestManager(int& argc, char** argv) // Big countdown watchdog QTimer::singleShot(180000, this, [this] { + clog << "Time is up, stopping the session"; if (testSuite) conclude(); else @@ -537,14 +538,32 @@ TEST_IMPL(setTopic) TEST_IMPL(changeName) { - auto* const localUser = connection()->user(); - const auto& newName = connection()->generateTxnId(); // See setTopic() - clog << "Renaming the user to " << newName.toStdString() << endl; - localUser->rename(newName); - connectUntil(localUser, &User::defaultNameChanged, this, - [this, thisTest, localUser, newName] { - FINISH_TEST(localUser->name() == newName); - }); + connectSingleShot(targetRoom, &Room::allMembersLoaded, this, [this, thisTest] { + auto* const localUser = connection()->user(); + const auto& newName = connection()->generateTxnId(); // See setTopic() + clog << "Renaming the user to " << newName.toStdString() + << " in the target room" << endl; + localUser->rename(newName, targetRoom); + connectUntil(targetRoom, &Room::memberRenamed, this, + [this, thisTest, localUser, newName](const User* u) { + if (localUser != u) + return false; + if (localUser->name(targetRoom) != newName) + FAIL_TEST(); + + clog + << "Member rename successful, renaming the account" + << endl; + const auto newN = newName.mid(0, 5); + localUser->rename(newN); + connectUntil(localUser, &User::defaultNameChanged, + this, [this, thisTest, localUser, newN] { + targetRoom->localUser()->rename({}); + FINISH_TEST(localUser->name() == newN); + }); + return true; + }); + }); return false; } -- cgit v1.2.3 From 24a562cc64e8fac6c55108ae2b7b6997ecdd2010 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 19 Dec 2021 10:43:33 +0100 Subject: Quotest: add a missing \n in the output --- quotest/quotest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index 8703efb2..7bd9c5c3 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -214,7 +214,7 @@ TestManager::TestManager(int& argc, char** argv) // Big countdown watchdog QTimer::singleShot(180000, this, [this] { - clog << "Time is up, stopping the session"; + clog << "Time is up, stopping the session\n"; if (testSuite) conclude(); else -- cgit v1.2.3 From 487e7f5ef75a5a5a4d732027d0b7705fdffb71ca Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 20 Dec 2021 16:18:35 +0100 Subject: Add event_loader.h to CMakeLists Another forgotten header file. --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2762df6a..7675daad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -147,6 +147,7 @@ list(APPEND lib_SRCS lib/accountregistry.h lib/accountregistry.cpp lib/mxcreply.h lib/mxcreply.cpp lib/events/event.h lib/events/event.cpp + lib/events/eventloader.h lib/events/roomevent.h lib/events/roomevent.cpp lib/events/stateevent.h lib/events/stateevent.cpp lib/events/simplestateevents.h -- cgit v1.2.3 From b989383165b648269a231d1febadc8150676d5cf Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 21 Dec 2021 18:40:47 +0100 Subject: Don't chain RoomEvent to Event factory any more Objects derived from Event are not room events (in the spec sense) and never occur in the same arrays as room events; therefore this chaining has always been superfluous. --- lib/events/roomevent.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index fb921af6..b728e0bf 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -9,9 +9,6 @@ using namespace Quotient; -[[maybe_unused]] static auto roomEventTypeInitialised = - Event::factory_t::chainFactory(); - RoomEvent::RoomEvent(Type type, event_mtype_t matrixType, const QJsonObject& contentJson) : Event(type, matrixType, contentJson) -- cgit v1.2.3 From 231c4d723eb53f3ea5f641b743d198584840a963 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 22 Dec 2021 13:44:39 +0100 Subject: Simplify the code around EventFactory<> The former code assumed that EventFactory<> is just a class-level shell for a bunch of functions and a static data member that only exists to allow specialisations to occur for the whole group together. On top of that, setupFactory() and registerEventType() strived to protect this group from double registration coming from static variables in an anonymous namespace produced by REGISTER_EVENT_TYPE. The whole thing is now de-static-ed: resolving the factory now relies on class-static Event/RoomEvent/StateEventBase::factory variables instead of factory_t type aliases; and REGISTER_EVENT_TYPE produces non-static inline variables instead, obviating the need of registerEventType/setupFactory kludge. --- lib/events/event.h | 166 +++++++++++++++++++++++-------------------- lib/events/eventloader.h | 18 ++--- lib/events/roomevent.h | 2 +- lib/events/roommemberevent.h | 32 ++++----- lib/events/stateevent.cpp | 15 ---- lib/events/stateevent.h | 18 ++++- 6 files changed, 125 insertions(+), 126 deletions(-) diff --git a/lib/events/event.h b/lib/events/event.h index 4d4bb16b..e786fb30 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -110,94 +110,90 @@ inline event_type_t typeId() inline event_type_t unknownEventTypeId() { return typeId(); } -// === EventFactory === +// === Event creation facilities === -/** Create an event of arbitrary type from its arguments */ +//! Create an event of arbitrary type from its arguments template inline event_ptr_tt makeEvent(ArgTs&&... args) { return std::make_unique(std::forward(args)...); } -template -class EventFactory { -public: - template - static auto addMethod(FnT&& method) - { - factories().emplace_back(std::forward(method)); - return 0; - } - - /** Chain two type factories - * Adds the factory class of EventT2 (EventT2::factory_t) to - * the list in factory class of EventT1 (EventT1::factory_t) so - * that when EventT1::factory_t::make() is invoked, types of - * EventT2 factory are looked through as well. This is used - * to include RoomEvent types into the more general Event factory, - * and state event types into the RoomEvent factory. - */ - template - static auto chainFactory() - { - return addMethod(&EventT::factory_t::make); - } - - static event_ptr_tt make(const QJsonObject& json, - const QString& matrixType) - { - for (const auto& f : factories()) - if (auto e = f(json, matrixType)) - return e; - return nullptr; - } - -private: - static auto& factories() +namespace _impl { + template + event_ptr_tt makeIfMatches(const QJsonObject& json, + const QString& matrixType) { - using inner_factory_tt = std::function( - const QJsonObject&, const QString&)>; - static std::vector _factories {}; - return _factories; + return QLatin1String(EventT::matrixTypeId()) == matrixType + ? makeEvent(json) + : nullptr; } -}; - -/** Add a type to its default factory - * Adds a standard factory method (via makeEvent<>) for a given - * type to EventT::factory_t factory class so that it can be - * created dynamically from loadEvent<>(). - * - * \tparam EventT the type to enable dynamic creation of - * \return the registered type id - * \sa loadEvent, Event::type - */ -template -inline auto setupFactory() -{ - qDebug(EVENTS) << "Adding factory method for" << EventT::matrixTypeId(); - return EventT::factory_t::addMethod([](const QJsonObject& json, - const QString& jsonMatrixType) { - return EventT::matrixTypeId() == jsonMatrixType ? makeEvent(json) - : nullptr; - }); -} -template -inline auto registerEventType() -{ - // Initialise exactly once, even if this function is called twice for - // the same type (for whatever reason - you never know the ways of - // static initialisation is done). - static const auto _ = setupFactory(); - return _; // Only to facilitate usage in static initialisation -} + //! \brief A family of event factories to create events from CS API responses + //! + //! Each of these factories, as instantiated by event base types (Event, + //! RoomEvent etc.) is capable of producing an event object derived from + //! \p BaseEventT, using the JSON payload and the event type passed to its + //! make() method. Don't use these directly to make events; use loadEvent() + //! overloads as the frontend for these. Never instantiate new factories + //! outside of base event classes. + //! \sa loadEvent, setupFactory, Event::factory, RoomEvent::factory, + //! StateEventBase::factory + template + class EventFactory + : private std::vector (*)(const QJsonObject&, + const QString&)> { + // Actual makeIfMatches specialisations will differ in the first + // template parameter but that doesn't affect the function type + public: + explicit EventFactory(const char* name = "") + : name(name) + { + static auto yetToBeConstructed = true; + Q_ASSERT(yetToBeConstructed); + if (!yetToBeConstructed) // For Release builds that pass Q_ASSERT + qCritical(EVENTS) + << "Another EventFactory for the same base type is being " + "created - event creation logic will be splintered"; + yetToBeConstructed = false; + } + EventFactory(const EventFactory&) = delete; + + //! \brief Add a method to create events of a given type + //! + //! Adds a standard factory method (makeIfMatches) for \p EventT so that + //! event objects of this type can be created dynamically by loadEvent. + //! The caller is responsible for ensuring this method is called only + //! once per type. + //! \sa makeIfMatches, loadEvent, Quotient::loadEvent + template + bool addMethod() + { + this->emplace_back(&makeIfMatches); + qDebug(EVENTS) << "Added factory method for" + << EventT::matrixTypeId() << "events;" << this->size() + << "methods in the" << name << "chain by now"; + return true; + } + + auto loadEvent(const QJsonObject& json, const QString& matrixType) + { + for (const auto& f : *this) + if (auto e = f(json, matrixType)) + return e; + return makeEvent(unknownEventTypeId(), json); + } + + const char* const name; + }; +} // namespace _impl // === Event === class Event { public: using Type = event_type_t; - using factory_t = EventFactory; + static inline _impl::EventFactory factory { "Event" }; explicit Event(Type type, const QJsonObject& json); explicit Event(Type type, event_mtype_t matrixType, @@ -272,7 +268,7 @@ template using EventsArray = std::vector>; using Events = EventsArray; -// === Macros used with event class definitions === +// === Facilities for event class definitions === // This macro should be used in a public section of an event class to // provide matrixTypeId() and typeId(). @@ -284,13 +280,27 @@ using Events = EventsArray; // This macro should be put after an event class definition (in .h or .cpp) // to enable its deserialisation from a /sync and other // polymorphic event arrays -#define REGISTER_EVENT_TYPE(_Type) \ - namespace { \ - [[maybe_unused]] static const auto _factoryAdded##_Type = \ - registerEventType<_Type>(); \ - } \ +#define REGISTER_EVENT_TYPE(_Type) \ + [[maybe_unused]] inline const auto _factoryAdded##_Type = \ + _Type::factory.addMethod<_Type>(); \ // End of macro +// === Event loading === +// (see also event_loader.h) + +//! \brief Point of customisation to dynamically load events +//! +//! The default specialisation of this calls BaseEventT::factory and if that +//! fails (i.e. returns nullptr) creates an unknown event of BaseEventT. +//! Other specialisations may reuse other factories, add validations common to +//! BaseEventT, and so on +template +event_ptr_tt doLoadEvent(const QJsonObject& json, + const QString& matrixType) +{ + return BaseEventT::factory.loadEvent(json, matrixType); +} + // === is<>(), eventCast<>() and switchOnType<>() === template diff --git a/lib/events/eventloader.h b/lib/events/eventloader.h index 978668f2..fe624d70 100644 --- a/lib/events/eventloader.h +++ b/lib/events/eventloader.h @@ -6,16 +6,6 @@ #include "stateevent.h" namespace Quotient { -namespace _impl { - template - static inline auto loadEvent(const QJsonObject& json, - const QString& matrixType) - { - if (auto e = EventFactory::make(json, matrixType)) - return e; - return makeEvent(unknownEventTypeId(), json); - } -} // namespace _impl /*! Create an event with proper type from a JSON object * @@ -26,7 +16,7 @@ namespace _impl { template inline event_ptr_tt loadEvent(const QJsonObject& fullJson) { - return _impl::loadEvent(fullJson, fullJson[TypeKeyL].toString()); + return doLoadEvent(fullJson, fullJson[TypeKeyL].toString()); } /*! Create an event from a type string and content JSON @@ -39,8 +29,8 @@ template inline event_ptr_tt loadEvent(const QString& matrixType, const QJsonObject& content) { - return _impl::loadEvent(basicEventJson(matrixType, content), - matrixType); + return doLoadEvent(basicEventJson(matrixType, content), + matrixType); } /*! Create a state event from a type string, content JSON and state key @@ -53,7 +43,7 @@ inline StateEventPtr loadStateEvent(const QString& matrixType, const QJsonObject& content, const QString& stateKey = {}) { - return _impl::loadEvent( + return doLoadEvent( basicStateEventJson(matrixType, content, stateKey), matrixType); } diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 7f13f6f2..8be58481 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -13,7 +13,7 @@ class RedactionEvent; /** This class corresponds to m.room.* events */ class RoomEvent : public Event { public: - using factory_t = EventFactory; + static inline _impl::EventFactory factory { "RoomEvent" }; // RedactionEvent is an incomplete type here so we cannot inline // constructors and destructors and we cannot use 'using'. diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h index f3047159..0fb464d4 100644 --- a/lib/events/roommemberevent.h +++ b/lib/events/roommemberevent.h @@ -49,16 +49,15 @@ public: std::forward(contentArgs)...) {} - /// A special constructor to create unknown RoomMemberEvents - /** - * This is needed in order to use RoomMemberEvent as a "base event - * class" in cases like GetMembersByRoomJob when RoomMemberEvents - * (rather than RoomEvents or StateEvents) are resolved from JSON. - * For such cases loadEvent<> requires an underlying class to be - * constructible with unknownTypeId() instead of its genuine id. - * Don't use it directly. - * \sa GetMembersByRoomJob, loadEvent, unknownTypeId - */ + //! \brief A special constructor to create unknown RoomMemberEvents + //! + //! This is needed in order to use RoomMemberEvent as a "base event class" + //! in cases like GetMembersByRoomJob when RoomMemberEvents (rather than + //! RoomEvents or StateEvents) are resolved from JSON. For such cases + //! loadEvent\<> requires an underlying class to have a specialisation of + //! EventFactory\<> and be constructible with unknownTypeId() instead of + //! its genuine id. Don't use directly. + //! \sa EventFactory, loadEvent, GetMembersByRoomJob RoomMemberEvent(Type type, const QJsonObject& fullJson) : StateEvent(type, fullJson) {} @@ -89,14 +88,13 @@ public: }; template <> -class EventFactory { -public: - static event_ptr_tt make(const QJsonObject& json, - const QString&) - { +inline event_ptr_tt +doLoadEvent(const QJsonObject& json, const QString& matrixType) +{ + if (matrixType == QLatin1String(RoomMemberEvent::matrixTypeId())) return makeEvent(json); - } -}; + return makeEvent(unknownEventTypeId(), json); +} REGISTER_EVENT_TYPE(RoomMemberEvent) } // namespace Quotient diff --git a/lib/events/stateevent.cpp b/lib/events/stateevent.cpp index efe011a0..9535af56 100644 --- a/lib/events/stateevent.cpp +++ b/lib/events/stateevent.cpp @@ -5,21 +5,6 @@ using namespace Quotient; -// Aside from the normal factory to instantiate StateEventBase inheritors -// StateEventBase itself can be instantiated if there's a state_key JSON key -// but the event type is unknown. -[[maybe_unused]] static auto stateEventTypeInitialised = - RoomEvent::factory_t::addMethod( - [](const QJsonObject& json, const QString& matrixType) -> StateEventPtr { - if (!json.contains(StateKeyKeyL)) - return nullptr; - - if (auto e = StateEventBase::factory_t::make(json, matrixType)) - return e; - - return makeEvent(unknownEventTypeId(), json); - }); - StateEventBase::StateEventBase(Event::Type type, event_mtype_t matrixType, const QString& stateKey, const QJsonObject& contentJson) diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index b0aa9907..919e8f86 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -19,7 +19,7 @@ inline QJsonObject basicStateEventJson(const QString& matrixTypeId, class StateEventBase : public RoomEvent { public: - using factory_t = EventFactory; + static inline _impl::EventFactory factory { "StateEvent" }; StateEventBase(Type type, const QJsonObject& json) : RoomEvent(type, json) {} @@ -37,6 +37,22 @@ public: using StateEventPtr = event_ptr_tt; using StateEvents = EventsArray; +//! \brief Override RoomEvent factory with that from StateEventBase if JSON has +//! stateKey +//! +//! This means in particular that an event with a type known to RoomEvent but +//! having stateKey set (even to an empty value) will be treated as a state +//! event and most likely end up as unknown (consider, e.g., m.room.message +//! that has stateKey set). +template <> +inline RoomEventPtr doLoadEvent(const QJsonObject& json, + const QString& matrixType) +{ + if (json.contains(StateKeyKeyL)) + return StateEventBase::factory.loadEvent(json, matrixType); + return RoomEvent::factory.loadEvent(json, matrixType); +} + template <> inline bool is(const Event& e) { -- cgit v1.2.3 From 060af9334049c58767b6457da2d7c07fdb0d171e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 22 Dec 2021 16:13:02 +0100 Subject: StateEventBase: force type to unknown if stateKey is not in JSON --- lib/events/stateevent.cpp | 8 ++++++++ lib/events/stateevent.h | 3 +-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/events/stateevent.cpp b/lib/events/stateevent.cpp index 9535af56..e53d47d4 100644 --- a/lib/events/stateevent.cpp +++ b/lib/events/stateevent.cpp @@ -5,6 +5,14 @@ using namespace Quotient; +StateEventBase::StateEventBase(Type type, const QJsonObject& json) + : RoomEvent(json.contains(StateKeyKeyL) ? type : unknownEventTypeId(), json) +{ + if (Event::type() == unknownEventTypeId() && !json.contains(StateKeyKeyL)) + qWarning(EVENTS) << "Attempt to create a state event with no stateKey -" + "forcing the event type to unknown to avoid damage"; +} + StateEventBase::StateEventBase(Event::Type type, event_mtype_t matrixType, const QString& stateKey, const QJsonObject& contentJson) diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index 919e8f86..c37965aa 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -21,8 +21,7 @@ class StateEventBase : public RoomEvent { public: static inline _impl::EventFactory factory { "StateEvent" }; - StateEventBase(Type type, const QJsonObject& json) : RoomEvent(type, json) - {} + StateEventBase(Type type, const QJsonObject& json); StateEventBase(Type type, event_mtype_t matrixType, const QString& stateKey = {}, const QJsonObject& contentJson = {}); -- cgit v1.2.3 From 42eaf3671c656088f8d038f83973f0931ad7051c Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Dec 2021 02:33:39 +0100 Subject: Sonar: leave just one job There's not much value in analysing the code without E2EE and with E2EE because E2EE is additive; and there's no plan to look close into the generated API code apart from what already ends up being committed. --- .github/workflows/sonar.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 76db59c9..ae96aebc 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -16,8 +16,8 @@ jobs: fail-fast: false matrix: qt-version: [ '5.12.10' ] - e2ee: [ '', 'e2ee' ] - update-api: [ '', 'update-api' ] + e2ee: [ 'e2ee' ] + update-api: [ '' ] env: SONAR_SCANNER_VERSION: 4.6.2.2472 -- cgit v1.2.3 From a08348007dd0a73a40b7b1d755b3affc963b5b80 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 22 Dec 2021 17:31:37 +0100 Subject: Sonar: add coverage analysis --- .github/workflows/sonar.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index ae96aebc..c8ddca66 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -53,7 +53,8 @@ jobs: echo "CXX=g++-10" >>$GITHUB_ENV mkdir -p $HOME/.sonar echo "CMAKE_ARGS=-G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=false \ - -DCMAKE_INSTALL_PREFIX=~/.local -DCMAKE_PREFIX_PATH=~/.local" >>$GITHUB_ENV + -DCMAKE_INSTALL_PREFIX=~/.local -DCMAKE_PREFIX_PATH=~/.local" \ + -DCMAKE_CXX_FLAGS=--coverage >>$GITHUB_ENV cmake -E make_directory ${{ runner.workspace }}/build - name: Build and install olm @@ -110,4 +111,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: | - $HOME/.sonar/sonar-scanner*/bin/sonar-scanner --define sonar.host.url="${{ env.SONAR_SERVER_URL }}" --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" + $HOME/.sonar/sonar-scanner*/bin/sonar-scanner \ + -Dsonar.host.url="${{ env.SONAR_SERVER_URL }}" \ + -Dsonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" \ + -Dsonar.cfamily.gcov.reportsPath=build/coverage -- cgit v1.2.3 From 0cbbae133d61ccb1fbb41a40660a70c65f65235f Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 23 Dec 2021 11:43:01 +0100 Subject: Merge Sonar invocation back to ci.yml For coverage analysis to work, a test run is needed, making the overlap between ci.yaml and sonar.yml quite significant again. Note: 'update-api' option is temporarily dropped from the matrix to speed up the check. If things run fine, 'update-api' will come back. --- .github/workflows/ci.yml | 59 +++++++++++++++++++--- .github/workflows/sonar.yml | 117 -------------------------------------------- 2 files changed, 51 insertions(+), 125 deletions(-) delete mode 100644 .github/workflows/sonar.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c78a5981..01a4468f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,16 +21,21 @@ jobs: matrix: os: [ubuntu-20.04, macos-10.15] compiler: [ GCC, Clang ] - platform: [ '' ] qt-version: [ '5.12.10' ] - qt-arch: [ '' ] # Not using binary values here, to make the job captions more readable e2ee: [ '' ] - update-api: [ '', 'update-api' ] + update-api: [ '' ] #, 'update-api' ] + sonar: [ '' ] + platform: [ '' ] + qt-arch: [ '' ] exclude: - os: macos-10.15 compiler: GCC include: + - os: ubuntu-latest + compiler: GCC + qt-version: '5.12.10' + sonar: 'sonar' - os: windows-2019 compiler: MSVC platform: x64 @@ -43,6 +48,9 @@ jobs: qt-arch: win64_msvc2017_64 update-api: update-api + env: + SONAR_SERVER_URL: 'https://sonarcloud.io' + steps: - uses: actions/checkout@v2 with: @@ -96,9 +104,18 @@ jobs: VERSION="$(git describe --all --contains)-ci${{ github.run_number }}-$(git rev-parse --short HEAD)" fi echo "QUOTEST_ORIGIN=$VERSION @ ${{ runner.os }}/${{ matrix.compiler }}" >>$GITHUB_ENV - echo "CMAKE_ARGS=-G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=false \ - -DCMAKE_INSTALL_PREFIX=~/.local -DCMAKE_PREFIX_PATH=~/.local\ - -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=ON" >>$GITHUB_ENV + + CMAKE_ARGS="-G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DBUILD_SHARED_LIBS=false \ + -DCMAKE_INSTALL_PREFIX=~/.local \ + -DCMAKE_PREFIX_PATH=~/.local \ + -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=ON" + + if [ -n "${{ matrix.sonar }}" ]; then + mkdir -p $HOME/.sonar + CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_CXX_FLAGS=--coverage" + fi + echo "CMAKE_ARGS=$CMAKE_ARGS" >>$GITHUB_ENV if [[ '${{ runner.os }}' != 'Windows' ]]; then BIN_DIR=/bin @@ -115,6 +132,20 @@ jobs: with: arch: ${{ matrix.platform }} + - name: Download and set up Sonar Cloud tools + if: matrix.sonar != '' + env: + SONAR_SCANNER_VERSION: 4.6.2.2472 + run: | + pushd $HOME/.sonar + curl -sSL --remote-name-all \ + $SONAR_SERVER_URL/static/cpp/build-wrapper-linux-x86.zip \ + https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$SONAR_SCANNER_VERSION-linux.zip + unzip -o build-wrapper*.zip + echo "BUILD_WRAPPER=$HOME/.sonar/build-wrapper-linux-x86/build-wrapper-linux* --out-dir $BUILD_PATH/sonar" >>$GITHUB_ENV + unzip -o sonar-scanner-cli*.zip + popd + - name: Build and install olm if: matrix.e2ee working-directory: ${{ runner.workspace }} @@ -158,7 +189,8 @@ jobs: - name: Build and install libQuotient run: | - cmake --build $BUILD_PATH --target all install + $BUILD_WRAPPER cmake --build $BUILD_PATH --target all + cmake --build $BUILD_PATH --target install ls ~/.local$BIN_DIR/quotest - name: Run tests @@ -172,7 +204,18 @@ jobs: [[ -z "$TEST_USER" ]] || \ $VALGRIND quotest "$TEST_USER" "$TEST_PWD" quotest-gha '#quotest:matrix.org' "$QUOTEST_ORIGIN" timeout-minutes: 4 # quotest is supposed to finish within 3 minutes, actually - + - name: Perform CodeQL analysis if: env.CODEQL_ANALYSIS uses: github/codeql-action/analyze@v1 + + - name: Run sonar-scanner + if: matrix.sonar != '' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: | + $HOME/.sonar/sonar-scanner*/bin/sonar-scanner \ + -Dsonar.host.url="$SONAR_SERVER_URL" \ + -Dsonar.cfamily.build-wrapper-output="$BUILD_PATH/sonar" \ + -Dsonar.cfamily.gcov.reportsPath="$BUILD_PATH/coverage" diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml deleted file mode 100644 index c8ddca66..00000000 --- a/.github/workflows/sonar.yml +++ /dev/null @@ -1,117 +0,0 @@ -name: Sonar - -on: - push: - pull_request: - types: [opened, reopened] - -defaults: - run: - shell: bash - -jobs: - SonarCloud: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - qt-version: [ '5.12.10' ] - e2ee: [ 'e2ee' ] - update-api: [ '' ] - - env: - SONAR_SCANNER_VERSION: 4.6.2.2472 - SONAR_SERVER_URL: "https://sonarcloud.io" - BUILD_WRAPPER_OUT_DIR: build/sonar - - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - submodules: ${{ matrix.e2ee != '' }} - - - name: Cache Qt - id: cache-qt - uses: actions/cache@v2 - with: - path: ${{ runner.workspace }}/Qt - key: ${{ runner.os }}-Qt${{ matrix.qt-version }}-cache - - - name: Install Qt - uses: jurplel/install-qt-action@v2.11.1 - with: - version: ${{ matrix.qt-version }} -# arch: ${{ matrix.qt-arch }} # Only Windows needs that - cached: ${{ steps.cache-qt.outputs.cache-hit }} - - - name: Install Ninja - uses: seanmiddleditch/gha-setup-ninja@v3 - - - name: Setup build environment - run: | - echo "CC=gcc-10" >>$GITHUB_ENV - echo "CXX=g++-10" >>$GITHUB_ENV - mkdir -p $HOME/.sonar - echo "CMAKE_ARGS=-G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=false \ - -DCMAKE_INSTALL_PREFIX=~/.local -DCMAKE_PREFIX_PATH=~/.local" \ - -DCMAKE_CXX_FLAGS=--coverage >>$GITHUB_ENV - cmake -E make_directory ${{ runner.workspace }}/build - - - name: Build and install olm - if: matrix.e2ee - run: | - cd .. - git clone https://gitlab.matrix.org/matrix-org/olm.git - cmake -S olm -B olm/build $CMAKE_ARGS - cmake --build olm/build --target install - - - name: Pull CS API and build GTAD - if: matrix.update-api - run: | - cd .. - git clone https://github.com/quotient-im/matrix-doc.git - git clone --recursive https://github.com/KitsuneRal/gtad.git - cmake -S gtad -B gtad $CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF - cmake --build gtad - echo "CMAKE_ARGS=$CMAKE_ARGS -DMATRIX_DOC_PATH=$GITHUB_WORKSPACE/../matrix-doc \ - -DGTAD_PATH=$GITHUB_WORKSPACE/../gtad/gtad" \ - >>$GITHUB_ENV - - - name: Download and set up Sonar Cloud tools - run: | - pushd $HOME/.sonar - curl -sSLo build-wrapper.zip $SONAR_SERVER_URL/static/cpp/build-wrapper-linux-x86.zip - unzip -o build-wrapper.zip - echo "BUILD_WRAPPER=$HOME/.sonar/build-wrapper-linux-x86/build-wrapper-linux* --out-dir $BUILD_WRAPPER_OUT_DIR" >>$GITHUB_ENV - curl -sSLo sonar-scanner.zip \ - https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$SONAR_SCANNER_VERSION-linux.zip - unzip -o sonar-scanner.zip - popd - - - name: Configure libQuotient - run: | - if [[ '${{ runner.os }}' == 'Windows' ]]; then - BIN_DIR=. - else - BIN_DIR=bin - fi - echo "BIN_DIR=$BIN_DIR" >>$GITHUB_ENV - cmake -S $GITHUB_WORKSPACE -B build $CMAKE_ARGS -DQuotient_ENABLE_E2EE=${{ matrix.e2ee }} - - - name: Regenerate API code - if: matrix.update-api - run: cmake --build build --target update-api - - - name: Build libQuotient - run: | - $BUILD_WRAPPER cmake --build build --target all - - - name: Run sonar-scanner - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: | - $HOME/.sonar/sonar-scanner*/bin/sonar-scanner \ - -Dsonar.host.url="${{ env.SONAR_SERVER_URL }}" \ - -Dsonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" \ - -Dsonar.cfamily.gcov.reportsPath=build/coverage -- cgit v1.2.3 From 669ed9bcae110ca0539876193ec3b8cb9b8a8a18 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Dec 2021 02:35:44 +0100 Subject: Actually do and submit coverage ...instead of hoping the thing will sort itself out because CLion does. --- .github/workflows/ci.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 01a4468f..8596ccd5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,6 +114,7 @@ jobs: if [ -n "${{ matrix.sonar }}" ]; then mkdir -p $HOME/.sonar CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_CXX_FLAGS=--coverage" + echo "COV=gcov$CXX_VERSION_POSTFIX" >>$GITHUB_ENV fi echo "CMAKE_ARGS=$CMAKE_ARGS" >>$GITHUB_ENV @@ -215,7 +216,11 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: | + mkdir .coverage && pushd .coverage + find $BUILD_PATH/CMakeFiles/Quotient.dir/lib -name '*.gcda' -print0 \ + | xargs -0 $COV -s $GITHUB_WORKSPACE/lib -pr + popd $HOME/.sonar/sonar-scanner*/bin/sonar-scanner \ -Dsonar.host.url="$SONAR_SERVER_URL" \ -Dsonar.cfamily.build-wrapper-output="$BUILD_PATH/sonar" \ - -Dsonar.cfamily.gcov.reportsPath="$BUILD_PATH/coverage" + -Dsonar.cfamily.gcov.reportsPath=.coverage -- cgit v1.2.3 From 3fa5925ad6c9ce558dd9726a02614fd6f9c031e5 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Dec 2021 02:35:56 +0100 Subject: Analyse in 2 threads --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8596ccd5..47c92ad3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -223,4 +223,5 @@ jobs: $HOME/.sonar/sonar-scanner*/bin/sonar-scanner \ -Dsonar.host.url="$SONAR_SERVER_URL" \ -Dsonar.cfamily.build-wrapper-output="$BUILD_PATH/sonar" \ + -Dsonar.cfamily.threads=2 \ -Dsonar.cfamily.gcov.reportsPath=.coverage -- cgit v1.2.3 From e1c19404846b2a7a2f321c662528252e4eeef35e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Dec 2021 06:42:36 +0100 Subject: Don't strip lib from names in .gcov files That apparently confuses Sonar as it fails to match the source files. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 47c92ad3..27c65b26 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -218,7 +218,7 @@ jobs: run: | mkdir .coverage && pushd .coverage find $BUILD_PATH/CMakeFiles/Quotient.dir/lib -name '*.gcda' -print0 \ - | xargs -0 $COV -s $GITHUB_WORKSPACE/lib -pr + | xargs -0 $COV -s $GITHUB_WORKSPACE -pr popd $HOME/.sonar/sonar-scanner*/bin/sonar-scanner \ -Dsonar.host.url="$SONAR_SERVER_URL" \ -- cgit v1.2.3 From a207439d165b00b689e37b2759e0ca42bdfb22ae Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Dec 2021 08:00:39 +0100 Subject: Reinstate update-api jobs --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 27c65b26..5a3a0708 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: qt-version: [ '5.12.10' ] # Not using binary values here, to make the job captions more readable e2ee: [ '' ] - update-api: [ '' ] #, 'update-api' ] + update-api: [ '', 'update-api' ] sonar: [ '' ] platform: [ '' ] qt-arch: [ '' ] -- cgit v1.2.3 From e4a9132ab5c684be9743823df7c31d12da1e3f3b Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Dec 2021 10:40:44 +0100 Subject: CI: Add missing coverage files --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a3a0708..72deb803 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -217,9 +217,11 @@ jobs: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: | mkdir .coverage && pushd .coverage - find $BUILD_PATH/CMakeFiles/Quotient.dir/lib -name '*.gcda' -print0 \ + find $BUILD_PATH -name '*.gcda' -print0 \ | xargs -0 $COV -s $GITHUB_WORKSPACE -pr popd + # Drop coverage of the test source code, which is obviously 100% + rm -f quotest* autotests* $HOME/.sonar/sonar-scanner*/bin/sonar-scanner \ -Dsonar.host.url="$SONAR_SERVER_URL" \ -Dsonar.cfamily.build-wrapper-output="$BUILD_PATH/sonar" \ -- cgit v1.2.3 From 179564eb4c2d5ee59ef129edab39ca1a7cbc4258 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Dec 2021 12:22:59 +0100 Subject: CI: Move a comment outside of the script Comments inside shell scripts apparently break the flimsy GHA machinery. --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 72deb803..5cac5874 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -215,12 +215,13 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + # Coverage of the test source code is not tracked, as it is obviously 100% + # (if it's not, some tests failed and break the build at an earlier stage) run: | mkdir .coverage && pushd .coverage find $BUILD_PATH -name '*.gcda' -print0 \ | xargs -0 $COV -s $GITHUB_WORKSPACE -pr popd - # Drop coverage of the test source code, which is obviously 100% rm -f quotest* autotests* $HOME/.sonar/sonar-scanner*/bin/sonar-scanner \ -Dsonar.host.url="$SONAR_SERVER_URL" \ -- cgit v1.2.3 From 165e17d4ad289dabdcb6a09ce27c02c6122b7f8a Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Dec 2021 13:37:55 +0100 Subject: CI: Fix rm being run in the wrong directory It's been alright with the comment inside the script. --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5cac5874..a1b6f0c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -215,14 +215,14 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - # Coverage of the test source code is not tracked, as it is obviously 100% - # (if it's not, some tests failed and break the build at an earlier stage) run: | mkdir .coverage && pushd .coverage find $BUILD_PATH -name '*.gcda' -print0 \ | xargs -0 $COV -s $GITHUB_WORKSPACE -pr - popd + # Coverage of the test source code is not tracked, as it is always 100% + # (if not, some tests failed and broke the build at an earlier stage) rm -f quotest* autotests* + popd $HOME/.sonar/sonar-scanner*/bin/sonar-scanner \ -Dsonar.host.url="$SONAR_SERVER_URL" \ -Dsonar.cfamily.build-wrapper-output="$BUILD_PATH/sonar" \ -- cgit v1.2.3 From ae0180acc50de443e98bc1c59dee94f0096df2e0 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 24 Dec 2021 15:40:13 +0100 Subject: Prune empty/ish call*event.cpp files --- CMakeLists.txt | 6 +++--- lib/events/callcandidatesevent.cpp | 27 --------------------------- lib/events/callhangupevent.cpp | 36 ------------------------------------ lib/events/callhangupevent.h | 8 ++++++-- 4 files changed, 9 insertions(+), 68 deletions(-) delete mode 100644 lib/events/callcandidatesevent.cpp delete mode 100644 lib/events/callhangupevent.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7675daad..adb5be7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -162,10 +162,10 @@ list(APPEND lib_SRCS lib/events/accountdataevents.h lib/events/receiptevent.h lib/events/receiptevent.cpp lib/events/reactionevent.h lib/events/reactionevent.cpp - lib/events/callanswerevent.h lib/events/callanswerevent.cpp - lib/events/callcandidatesevent.h lib/events/callcandidatesevent.cpp - lib/events/callhangupevent.h lib/events/callhangupevent.cpp lib/events/callinviteevent.h lib/events/callinviteevent.cpp + lib/events/callcandidatesevent.h + lib/events/callanswerevent.h lib/events/callanswerevent.cpp + lib/events/callhangupevent.h lib/events/directchatevent.h lib/events/directchatevent.cpp lib/events/encryptionevent.h lib/events/encryptionevent.cpp lib/events/encryptedevent.h lib/events/encryptedevent.cpp diff --git a/lib/events/callcandidatesevent.cpp b/lib/events/callcandidatesevent.cpp deleted file mode 100644 index b87c8e9b..00000000 --- a/lib/events/callcandidatesevent.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2017 Marius Gripsgard -// SPDX-FileCopyrightText: 2018 Josip Delic -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "callcandidatesevent.h" - -/* -m.call.candidates -{ - "age": 242352, - "content": { - "call_id": "12345", - "candidates": [ - { - "candidate": "candidate:863018703 1 udp 2122260223 10.9.64.156 -43670 typ host generation 0", "sdpMLineIndex": 0, "sdpMid": "audio" - } - ], - "version": 0 - }, - "event_id": "$WLGTSEFSEF:localhost", - "origin_server_ts": 1431961217939, - "room_id": "!Cuyf34gef24t:localhost", - "sender": "@example:localhost", - "type": "m.call.candidates" -} -*/ diff --git a/lib/events/callhangupevent.cpp b/lib/events/callhangupevent.cpp deleted file mode 100644 index 43bc4db0..00000000 --- a/lib/events/callhangupevent.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2017 Marius Gripsgard - * SPDX-FileCopyrightText: 2018 Josip Delic - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ - -#include "callhangupevent.h" - -/* -m.call.hangup -{ - "age": 242352, - "content": { - "call_id": "12345", - "version": 0 - }, - "event_id": "$WLGTSEFSEF:localhost", - "origin_server_ts": 1431961217939, - "room_id": "!Cuyf34gef24t:localhost", - "sender": "@example:localhost", - "type": "m.call.hangup" -} -*/ - -using namespace Quotient; - -CallHangupEvent::CallHangupEvent(const QJsonObject& obj) - : CallEventBase(typeId(), obj) -{ - qCDebug(EVENTS) << "Call Hangup event"; -} - -CallHangupEvent::CallHangupEvent(const QString& callId) - : CallEventBase(typeId(), matrixTypeId(), callId, 0) -{} diff --git a/lib/events/callhangupevent.h b/lib/events/callhangupevent.h index 24382ac2..f3f82833 100644 --- a/lib/events/callhangupevent.h +++ b/lib/events/callhangupevent.h @@ -11,8 +11,12 @@ class CallHangupEvent : public CallEventBase { public: DEFINE_EVENT_TYPEID("m.call.hangup", CallHangupEvent) - explicit CallHangupEvent(const QJsonObject& obj); - explicit CallHangupEvent(const QString& callId); + explicit CallHangupEvent(const QJsonObject& obj) + : CallEventBase(typeId(), obj) + {} + explicit CallHangupEvent(const QString& callId) + : CallEventBase(typeId(), matrixTypeId(), callId, 0) + {} }; REGISTER_EVENT_TYPE(CallHangupEvent) -- cgit v1.2.3 From ebff6633ad464991fdffed1841ec6dc5b34296a2 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Sat, 25 Dec 2021 23:46:14 +0100 Subject: Make canChangePassword available from QML --- lib/connection.h | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/connection.h b/lib/connection.h index 05a3bb7f..52bf3cea 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -124,6 +124,7 @@ class Connection : public QObject { Q_PROPERTY(bool supportsPasswordAuth READ supportsPasswordAuth NOTIFY loginFlowsChanged STORED false) Q_PROPERTY(bool cacheState READ cacheState WRITE setCacheState NOTIFY cacheStateChanged) Q_PROPERTY(bool lazyLoading READ lazyLoading WRITE setLazyLoading NOTIFY lazyLoadingChanged) + Q_PROPERTY(bool canChangePassword READ canChangePassword NOTIFY capabilitiesLoaded) public: using UsersToDevicesToEvents = -- cgit v1.2.3 From 53c494f1b9f273395caade35c93e3fb520d083ec Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 27 Dec 2021 20:15:47 +0100 Subject: Quotest: add compile warnings from libQuotient --- quotest/CMakeLists.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/quotest/CMakeLists.txt b/quotest/CMakeLists.txt index 8d67b6f1..cb41141d 100644 --- a/quotest/CMakeLists.txt +++ b/quotest/CMakeLists.txt @@ -8,6 +8,21 @@ find_package(${Qt} COMPONENTS Concurrent) add_executable(quotest ${quotest_SRCS}) target_link_libraries(quotest PRIVATE ${Qt}::Core ${Qt}::Test ${Qt}::Concurrent ${PROJECT_NAME}) +if (MSVC) + target_compile_options(quotest PUBLIC /EHsc /W4 + /wd4100 /wd4127 /wd4242 /wd4244 /wd4245 /wd4267 /wd4365 /wd4456 /wd4459 + /wd4464 /wd4505 /wd4514 /wd4571 /wd4619 /wd4623 /wd4625 /wd4626 /wd4706 + /wd4710 /wd4774 /wd4820 /wd4946 /wd5026 /wd5027) +else() + foreach (FLAG W Wall Wpedantic Wextra Wno-unused-parameter Werror=return-type) + CHECK_CXX_COMPILER_FLAG("-${FLAG}" COMPILER_${FLAG}_SUPPORTED) + if (COMPILER_${FLAG}_SUPPORTED AND + NOT CMAKE_CXX_FLAGS MATCHES "(^| )-?${FLAG}($| )") + target_compile_options(quotest PUBLIC -${FLAG}) + endif () + endforeach () +endif() + option(${PROJECT_NAME}_INSTALL_TESTS "install quotest application" ON) add_feature_info(InstallQuotest ${PROJECT_NAME}_INSTALL_TESTS "the library functional test suite") -- cgit v1.2.3 From 58b2501aeecf2c169fd1f583dca292169568b5fa Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 26 Dec 2021 09:29:00 +0100 Subject: Connection: Simplify room/user factory code There's no need to return lambdas where pointers to specialised function templates would work just fine. --- lib/connection.cpp | 4 ++-- lib/connection.h | 14 ++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 25219def..8d1c80f1 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -1513,8 +1513,8 @@ room_factory_t Connection::roomFactory() { return _roomFactory; } user_factory_t Connection::userFactory() { return _userFactory; } -room_factory_t Connection::_roomFactory = defaultRoomFactory<>(); -user_factory_t Connection::_userFactory = defaultUserFactory<>(); +room_factory_t Connection::_roomFactory = defaultRoomFactory<>; +user_factory_t Connection::_userFactory = defaultUserFactory<>; QByteArray Connection::generateTxnId() const { diff --git a/lib/connection.h b/lib/connection.h index 52bf3cea..0713af16 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -81,11 +81,9 @@ using user_factory_t = std::function; * \sa Connection::setRoomFactory, Connection::setRoomType */ template -static inline room_factory_t defaultRoomFactory() +auto defaultRoomFactory(Connection* c, const QString& id, JoinState js) { - return [](Connection* c, const QString& id, JoinState js) { - return new T(c, id, js); - }; + return new T(c, id, js); } /** The default factory to create user objects @@ -94,9 +92,9 @@ static inline room_factory_t defaultRoomFactory() * \sa Connection::setUserFactory, Connection::setUserType */ template -static inline user_factory_t defaultUserFactory() +auto defaultUserFactory(Connection* c, const QString& id) { - return [](Connection* c, const QString& id) { return new T(id, c); }; + return new T(id, c); } // Room ids, rather than room pointers, are used in the direct chat @@ -469,14 +467,14 @@ public: template static void setRoomType() { - setRoomFactory(defaultRoomFactory()); + setRoomFactory(defaultRoomFactory); } /// Set the user factory to default with the overriden user type template static void setUserType() { - setUserFactory(defaultUserFactory()); + setUserFactory(defaultUserFactory); } public Q_SLOTS: -- cgit v1.2.3 From 674e984e459375974f619d0e778d43a2cc928dc3 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 26 Dec 2021 09:33:14 +0100 Subject: Key* strings: drop 'static'; add 'constexpr' where ok --- lib/events/event.h | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/events/event.h b/lib/events/event.h index e786fb30..8f62872d 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -29,24 +29,24 @@ inline TargetEventT* weakPtrCast(const event_ptr_tt& ptr) // === Standard Matrix key names and basicEventJson() === -static const auto TypeKey = QStringLiteral("type"); -static const auto BodyKey = QStringLiteral("body"); -static const auto ContentKey = QStringLiteral("content"); -static const auto EventIdKey = QStringLiteral("event_id"); -static const auto SenderKey = QStringLiteral("sender"); -static const auto RoomIdKey = QStringLiteral("room_id"); -static const auto UnsignedKey = QStringLiteral("unsigned"); -static const auto StateKeyKey = QStringLiteral("state_key"); -static const auto TypeKeyL = "type"_ls; -static const auto BodyKeyL = "body"_ls; -static const auto ContentKeyL = "content"_ls; -static const auto EventIdKeyL = "event_id"_ls; -static const auto SenderKeyL = "sender"_ls; -static const auto RoomIdKeyL = "room_id"_ls; -static const auto UnsignedKeyL = "unsigned"_ls; -static const auto RedactedCauseKeyL = "redacted_because"_ls; -static const auto PrevContentKeyL = "prev_content"_ls; -static const auto StateKeyKeyL = "state_key"_ls; +constexpr auto TypeKeyL = "type"_ls; +constexpr auto BodyKeyL = "body"_ls; +constexpr auto ContentKeyL = "content"_ls; +constexpr auto EventIdKeyL = "event_id"_ls; +constexpr auto SenderKeyL = "sender"_ls; +constexpr auto RoomIdKeyL = "room_id"_ls; +constexpr auto UnsignedKeyL = "unsigned"_ls; +constexpr auto RedactedCauseKeyL = "redacted_because"_ls; +constexpr auto PrevContentKeyL = "prev_content"_ls; +constexpr auto StateKeyKeyL = "state_key"_ls; +const QString TypeKey { TypeKeyL }; +const QString BodyKey { BodyKeyL }; +const QString ContentKey { ContentKeyL }; +const QString EventIdKey { EventIdKeyL }; +const QString SenderKey { SenderKeyL }; +const QString RoomIdKey { RoomIdKeyL }; +const QString UnsignedKey { UnsignedKeyL }; +const QString StateKeyKey { StateKeyKeyL }; /// Make a minimal correct Matrix event JSON inline QJsonObject basicEventJson(const QString& matrixType, -- cgit v1.2.3 From 3e0ca5db7698d59c6f2e9e7ae3a3a1654641eb4e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 26 Dec 2021 10:38:49 +0100 Subject: EventItem::setUserData: use std::move Fixes a clang-tidy warning. --- lib/eventitem.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/eventitem.h b/lib/eventitem.h index 0ab1a01d..b411a90c 100644 --- a/lib/eventitem.h +++ b/lib/eventitem.h @@ -57,7 +57,7 @@ public: } /// Store arbitrary data with the event item - void setUserData(std::any userData) { data = userData; } + void setUserData(std::any userData) { data = std::move(userData); } /// Obtain custom data previously stored with the event item const std::any& userdata() const { return data; } std::any& userData() { return data; } -- cgit v1.2.3 From 5f4d9ef6df492bfd91fa7c48fb4d5f8c2afc40b2 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 27 Dec 2021 20:14:07 +0100 Subject: EventFactory: remove default constructor This is a leftover from deferred `name` initialisation that wasn't needed in the end. --- lib/events/event.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/events/event.h b/lib/events/event.h index 8f62872d..8347bb4f 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -146,7 +146,7 @@ namespace _impl { // Actual makeIfMatches specialisations will differ in the first // template parameter but that doesn't affect the function type public: - explicit EventFactory(const char* name = "") + explicit EventFactory(const char* name) : name(name) { static auto yetToBeConstructed = true; -- cgit v1.2.3 From 7bb59b7a4a8b8804fa1eb5e631463da6a6cfdf84 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 28 Dec 2021 08:33:56 +0100 Subject: Convert struct LoginFlows to namespace Because that's what it really is. --- lib/connection.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/connection.h b/lib/connection.h index 0713af16..45abb8ed 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -51,11 +51,11 @@ class LeaveRoomJob; using LoginFlow = GetLoginFlowsJob::LoginFlow; /// Predefined login flows -struct LoginFlows { - static inline const LoginFlow Password { "m.login.password" }; - static inline const LoginFlow SSO { "m.login.sso" }; - static inline const LoginFlow Token { "m.login.token" }; -}; +namespace LoginFlows { + inline const LoginFlow Password { "m.login.password" }; + inline const LoginFlow SSO { "m.login.sso" }; + inline const LoginFlow Token { "m.login.token" }; +} // To simplify comparisons of LoginFlows -- cgit v1.2.3 From 0d9428bfbd997ac2073a470e59749cda81e0b213 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 28 Dec 2021 09:01:37 +0100 Subject: Settings classes: unify on QUO_ macro prefix At some point macros were prepended with QTNT (pronounced "cute-n't", "Quotient" with vowels dropped) but that didn't go very far. Having forgotten about this, I introduced QUO prefix in a few places. Being initial letters of "Quotient", QUO feels more understandable (and coincidentally is a well-known Latin word); so let's unify on this. --- lib/networksettings.cpp | 6 +++--- lib/networksettings.h | 6 +++--- lib/settings.cpp | 6 +++--- lib/settings.h | 10 +++++----- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/networksettings.cpp b/lib/networksettings.cpp index ce46ce5f..06b1fdf9 100644 --- a/lib/networksettings.cpp +++ b/lib/networksettings.cpp @@ -11,9 +11,9 @@ void NetworkSettings::setupApplicationProxy() const { proxyType(), proxyHostName(), proxyPort() }); } -QTNT_DEFINE_SETTING(NetworkSettings, QNetworkProxy::ProxyType, proxyType, +QUO_DEFINE_SETTING(NetworkSettings, QNetworkProxy::ProxyType, proxyType, "proxy_type", QNetworkProxy::DefaultProxy, setProxyType) -QTNT_DEFINE_SETTING(NetworkSettings, QString, proxyHostName, "proxy_hostname", +QUO_DEFINE_SETTING(NetworkSettings, QString, proxyHostName, "proxy_hostname", {}, setProxyHostName) -QTNT_DEFINE_SETTING(NetworkSettings, quint16, proxyPort, "proxy_port", -1, +QUO_DEFINE_SETTING(NetworkSettings, quint16, proxyPort, "proxy_port", -1, setProxyPort) diff --git a/lib/networksettings.h b/lib/networksettings.h index df11a9c8..b83a0e15 100644 --- a/lib/networksettings.h +++ b/lib/networksettings.h @@ -12,9 +12,9 @@ Q_DECLARE_METATYPE(QNetworkProxy::ProxyType) namespace Quotient { class NetworkSettings : public SettingsGroup { Q_OBJECT - QTNT_DECLARE_SETTING(QNetworkProxy::ProxyType, proxyType, setProxyType) - QTNT_DECLARE_SETTING(QString, proxyHostName, setProxyHostName) - QTNT_DECLARE_SETTING(quint16, proxyPort, setProxyPort) + QUO_DECLARE_SETTING(QNetworkProxy::ProxyType, proxyType, setProxyType) + QUO_DECLARE_SETTING(QString, proxyHostName, setProxyHostName) + QUO_DECLARE_SETTING(quint16, proxyPort, setProxyPort) Q_PROPERTY(QString proxyHost READ proxyHostName WRITE setProxyHostName) public: template diff --git a/lib/settings.cpp b/lib/settings.cpp index ed9082b0..5549e4de 100644 --- a/lib/settings.cpp +++ b/lib/settings.cpp @@ -102,11 +102,11 @@ void SettingsGroup::remove(const QString& key) Settings::remove(fullKey); } -QTNT_DEFINE_SETTING(AccountSettings, QString, deviceId, "device_id", {}, +QUO_DEFINE_SETTING(AccountSettings, QString, deviceId, "device_id", {}, setDeviceId) -QTNT_DEFINE_SETTING(AccountSettings, QString, deviceName, "device_name", {}, +QUO_DEFINE_SETTING(AccountSettings, QString, deviceName, "device_name", {}, setDeviceName) -QTNT_DEFINE_SETTING(AccountSettings, bool, keepLoggedIn, "keep_logged_in", false, +QUO_DEFINE_SETTING(AccountSettings, bool, keepLoggedIn, "keep_logged_in", false, setKeepLoggedIn) static const auto HomeserverKey = QStringLiteral("homeserver"); diff --git a/lib/settings.h b/lib/settings.h index efd0d714..3cab8958 100644 --- a/lib/settings.h +++ b/lib/settings.h @@ -104,7 +104,7 @@ private: QString groupPath; }; -#define QTNT_DECLARE_SETTING(type, propname, setter) \ +#define QUO_DECLARE_SETTING(type, propname, setter) \ Q_PROPERTY(type propname READ propname WRITE setter) \ public: \ type propname() const; \ @@ -112,7 +112,7 @@ public: \ \ private: -#define QTNT_DEFINE_SETTING(classname, type, propname, qsettingname, \ +#define QUO_DEFINE_SETTING(classname, type, propname, qsettingname, \ defaultValue, setter) \ type classname::propname() const \ { \ @@ -127,9 +127,9 @@ private: class AccountSettings : public SettingsGroup { Q_OBJECT Q_PROPERTY(QString userId READ userId CONSTANT) - QTNT_DECLARE_SETTING(QString, deviceId, setDeviceId) - QTNT_DECLARE_SETTING(QString, deviceName, setDeviceName) - QTNT_DECLARE_SETTING(bool, keepLoggedIn, setKeepLoggedIn) + QUO_DECLARE_SETTING(QString, deviceId, setDeviceId) + QUO_DECLARE_SETTING(QString, deviceName, setDeviceName) + QUO_DECLARE_SETTING(bool, keepLoggedIn, setKeepLoggedIn) Q_PROPERTY(QByteArray encryptionAccountPickle READ encryptionAccountPickle WRITE setEncryptionAccountPickle) public: -- cgit v1.2.3 From 418b85c285fa0a0c196a26eef5cc0c9c3dbe20fe Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 28 Dec 2021 09:07:37 +0100 Subject: EventContent::FileInfo: default payloadSize to 0 Fixes a clang-tidy warning. --- lib/events/eventcontent.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index f609a603..f6dbd4bf 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -105,7 +105,7 @@ namespace EventContent { QJsonObject originalInfoJson; QMimeType mimeType; QUrl url; - qint64 payloadSize; + qint64 payloadSize = 0; QString originalName; Omittable file = none; }; -- cgit v1.2.3 From 474d71067462f821697111a727e5f56767d2b00e Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 29 Dec 2021 18:07:08 +0100 Subject: Introduce quotient_export.h Instead of using CMake's generate_export_header macro, it's a bit easier to maintain a static file (that is not supposed to ever change) with necessary export/import/hidden visibility macros. --- CMakeLists.txt | 1 + lib/quotient_export.h | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 lib/quotient_export.h diff --git a/CMakeLists.txt b/CMakeLists.txt index adb5be7b..0fc47b42 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -125,6 +125,7 @@ endif () # Set up source files list(APPEND lib_SRCS lib/quotient_common.h + lib/quotient_export.h lib/function_traits.h lib/function_traits.cpp lib/networkaccessmanager.h lib/networkaccessmanager.cpp lib/connectiondata.h lib/connectiondata.cpp diff --git a/lib/quotient_export.h b/lib/quotient_export.h new file mode 100644 index 00000000..5a6edb0e --- /dev/null +++ b/lib/quotient_export.h @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2021 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#ifdef QUOTIENT_STATIC +# define QUOTIENT_API +# define QUOTIENT_HIDDEN +#else +# ifndef QUOTIENT_API +# ifdef BUILDING_SHARED_QUOTIENT + /* We are building this library */ +# define QUOTIENT_API Q_DECL_EXPORT +# else + /* We are using this library */ +# define QUOTIENT_API Q_DECL_IMPORT +# endif +# endif + +# ifndef QUOTIENT_HIDDEN +# define QUOTIENT_HIDDEN Q_DECL_HIDDEN +# endif +#endif -- cgit v1.2.3 From 02fdd08b98d878168eb81376a44586176dfd9576 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 26 Dec 2021 10:40:56 +0100 Subject: Quotest: test sending and receiving custom events This seems to be the crux of #413. --- quotest/quotest.cpp | 57 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index 7bd9c5c3..5a5642fe 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -101,6 +101,7 @@ private slots: TEST_DECL(sendMessage) TEST_DECL(sendReaction) TEST_DECL(sendFile) + TEST_DECL(sendCustomEvent) TEST_DECL(setTopic) TEST_DECL(changeName) TEST_DECL(sendAndRedact) @@ -125,6 +126,7 @@ private: [[nodiscard]] bool checkRedactionOutcome(const QByteArray& thisTest, const QString& evtIdToRedact); + template [[nodiscard]] bool validatePendingEvent(const QString& txnId); [[nodiscard]] bool checkDirectChat() const; void finishTest(const TestToken& token, bool condition, const char* file, @@ -152,12 +154,14 @@ void TestSuite::doTest(const QByteArray& testName) Q_ARG(TestToken, testName)); } +template bool TestSuite::validatePendingEvent(const QString& txnId) { auto it = targetRoom->findPendingEvent(txnId); return it != targetRoom->pendingEvents().end() && it->deliveryStatus() == EventStatus::Submitted - && (*it)->transactionId() == txnId; + && (*it)->transactionId() == txnId && is(**it) + && (*it)->matrixType() == EventT::matrixTypeId(); } void TestSuite::finishTest(const TestToken& token, bool condition, @@ -341,7 +345,7 @@ TEST_IMPL(loadMembers) TEST_IMPL(sendMessage) { auto txnId = targetRoom->postPlainText("Hello, " % origin % " is here"); - if (!validatePendingEvent(txnId)) { + if (!validatePendingEvent(txnId)) { clog << "Invalid pending event right after submitting" << endl; FAIL_TEST(); } @@ -367,7 +371,7 @@ TEST_IMPL(sendReaction) const auto targetEvtId = targetRoom->messageEvents().back()->id(); const auto key = QStringLiteral("+1"); const auto txnId = targetRoom->postReaction(targetEvtId, key); - if (!validatePendingEvent(txnId)) { + if (!validatePendingEvent(txnId)) { clog << "Invalid pending event right after submitting" << endl; FAIL_TEST(); } @@ -409,7 +413,7 @@ TEST_IMPL(sendFile) clog << "Sending file " << tfName.toStdString() << endl; const auto txnId = targetRoom->postFile( "Test file", new EventContent::FileContent(tfi)); - if (!validatePendingEvent(txnId)) { + if (!validatePendingEvent(txnId)) { clog << "Invalid pending event right after submitting" << endl; tf->deleteLater(); FAIL_TEST(); @@ -518,6 +522,50 @@ bool TestSuite::checkFileSendingOutcome(const TestToken& thisTest, return true; } +class CustomEvent : public RoomEvent { +public: + DEFINE_EVENT_TYPEID("quotest.custom", CustomEvent) + + CustomEvent(const QJsonObject& jo) + : RoomEvent(typeId(), jo) + {} + CustomEvent(int testValue) + : RoomEvent(typeId(), + basicEventJson(matrixTypeId(), + QJsonObject { { "testValue"_ls, + toJson(testValue) } })) + {} + + auto testValue() const { return contentPart("testValue"_ls); } +}; +REGISTER_EVENT_TYPE(CustomEvent) + +TEST_IMPL(sendCustomEvent) +{ + auto txnId = targetRoom->postEvent(new CustomEvent(42)); + if (!validatePendingEvent(txnId)) { + clog << "Invalid pending event right after submitting" << endl; + FAIL_TEST(); + } + connectUntil( + targetRoom, &Room::pendingEventAboutToMerge, this, + [this, thisTest, txnId](const RoomEvent* evt, int pendingIdx) { + const auto& pendingEvents = targetRoom->pendingEvents(); + Q_ASSERT(pendingIdx >= 0 && pendingIdx < int(pendingEvents.size())); + + if (evt->transactionId() != txnId) + return false; + + return switchOnType(*evt, + [this, thisTest, &evt](const CustomEvent& e) { + FINISH_TEST(!evt->id().isEmpty() && e.testValue() == 42); + }, + [this, thisTest] (const RoomEvent&) { FAIL_TEST(); }); + }); + return false; + +} + TEST_IMPL(setTopic) { const auto newTopic = connection()->generateTxnId(); // Just a way to make @@ -567,7 +615,6 @@ TEST_IMPL(changeName) return false; } - TEST_IMPL(showLocalUsername) { auto* const localUser = connection()->user(); -- cgit v1.2.3 From 7350fe82953cf6274b8845a890eafb21a09b9931 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 29 Dec 2021 15:59:58 +0100 Subject: Add QUOTIENT_API throughout non-generated code This include all (hopefully) classes/structures and functions that have non-inline definitions, as well as namespaces with Q_NAMESPACE since those have non-inline (as of Qt 5.15) QMetaObject - for that a new macro, QUO_NAMESPACE, has been devised to accommodate the lack of Q_NAMESPACE_EXPORT in Qt before 5.14. --- lib/accountregistry.h | 4 +++- lib/avatar.h | 4 +++- lib/connection.h | 2 +- lib/converters.h | 6 +++--- lib/eventitem.cpp | 4 ++++ lib/eventitem.h | 9 +++++---- lib/events/accountdataevents.h | 2 +- lib/events/callanswerevent.h | 3 +-- lib/events/callhangupevent.h | 2 +- lib/events/callinviteevent.h | 2 +- lib/events/directchatevent.h | 2 +- lib/events/encryptedevent.h | 2 +- lib/events/encryptionevent.h | 5 ++--- lib/events/event.h | 23 +++++++++++++---------- lib/events/eventcontent.h | 19 ++++++++++--------- lib/events/reactionevent.h | 6 +++--- lib/events/receiptevent.h | 2 +- lib/events/roomavatarevent.h | 3 ++- lib/events/roomcreateevent.h | 2 +- lib/events/roomevent.h | 4 ++-- lib/events/roomkeyevent.h | 2 +- lib/events/roommemberevent.h | 5 ++--- lib/events/roommessageevent.h | 8 ++++---- lib/events/roompowerlevelsevent.h | 8 +++----- lib/events/roomtombstoneevent.h | 2 +- lib/events/simplestateevents.h | 3 ++- lib/events/stateevent.h | 2 +- lib/events/stickerevent.h | 2 +- lib/events/typingevent.h | 2 +- lib/eventstats.h | 4 ++-- lib/jobs/basejob.h | 4 ++-- lib/jobs/downloadfilejob.h | 2 +- lib/jobs/mediathumbnailjob.h | 2 +- lib/jobs/requestdata.h | 4 +++- lib/mxcreply.h | 4 +++- lib/networkaccessmanager.h | 4 +++- lib/networksettings.h | 2 +- lib/quotient_common.h | 17 ++++++++++++++++- lib/room.h | 8 ++++---- lib/settings.h | 8 +++++--- lib/ssosession.h | 4 +++- lib/syncdata.h | 2 +- lib/uri.h | 2 +- lib/uriresolver.h | 6 +++--- lib/user.h | 3 ++- lib/util.h | 22 ++++++++++++---------- 46 files changed, 138 insertions(+), 100 deletions(-) diff --git a/lib/accountregistry.h b/lib/accountregistry.h index 5efda459..f7a864df 100644 --- a/lib/accountregistry.h +++ b/lib/accountregistry.h @@ -4,6 +4,8 @@ #pragma once +#include "quotient_export.h" + #include #include #include @@ -11,7 +13,7 @@ namespace Quotient { class Connection; -class AccountRegistry : public QAbstractListModel { +class QUOTIENT_API AccountRegistry : public QAbstractListModel { Q_OBJECT public: enum EventRoles { diff --git a/lib/avatar.h b/lib/avatar.h index d4634aea..93f43948 100644 --- a/lib/avatar.h +++ b/lib/avatar.h @@ -3,6 +3,8 @@ #pragma once +#include "quotient_export.h" + #include #include @@ -12,7 +14,7 @@ namespace Quotient { class Connection; -class Avatar { +class QUOTIENT_API Avatar { public: explicit Avatar(); explicit Avatar(QUrl url); diff --git a/lib/connection.h b/lib/connection.h index 0713af16..d669462e 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -105,7 +105,7 @@ using DirectChatsMap = QMultiHash; using DirectChatUsersMap = QMultiHash; using IgnoredUsersList = IgnoredUsersEvent::content_type; -class Connection : public QObject { +class QUOTIENT_API Connection : public QObject { Q_OBJECT Q_PROPERTY(User* localUser READ user NOTIFY stateChanged) diff --git a/lib/converters.h b/lib/converters.h index 9c3d5749..a6028f1b 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -183,7 +183,7 @@ struct JsonConverter { }; template <> -struct JsonConverter { +struct QUOTIENT_API JsonConverter { static QJsonValue dump(const QVariant& v); static QVariant load(const QJsonValue& jv); }; @@ -281,9 +281,9 @@ template struct JsonObjectConverter> : public HashMapFromJson> {}; -QJsonObject toJson(const QVariantHash& vh); +QJsonObject QUOTIENT_API toJson(const QVariantHash& vh); template <> -QVariantHash fromJson(const QJsonValue& jv); +QVariantHash QUOTIENT_API fromJson(const QJsonValue& jv); // Conditional insertion into a QJsonObject diff --git a/lib/eventitem.cpp b/lib/eventitem.cpp index 4f1595bc..a2d65d8d 100644 --- a/lib/eventitem.cpp +++ b/lib/eventitem.cpp @@ -25,3 +25,7 @@ void PendingEventItem::setFileUploaded(const QUrl& remoteUrl) } setStatus(EventStatus::FileUploaded); } + +// Not exactly sure why but this helps with the linker not finding +// Quotient::EventStatus::staticMetaObject when building Quaternion +#include "moc_eventitem.cpp" diff --git a/lib/eventitem.h b/lib/eventitem.h index 0ab1a01d..2f1e72cc 100644 --- a/lib/eventitem.h +++ b/lib/eventitem.h @@ -4,6 +4,7 @@ #pragma once #include "events/stateevent.h" +#include "quotient_common.h" #include #include @@ -11,7 +12,7 @@ namespace Quotient { namespace EventStatus { - Q_NAMESPACE + QUO_NAMESPACE /** Special marks an event can assume * @@ -33,7 +34,7 @@ namespace EventStatus { Q_ENUM_NS(Code) } // namespace EventStatus -class EventItemBase { +class QUOTIENT_API EventItemBase { public: explicit EventItemBase(RoomEventPtr&& e) : evt(std::move(e)) { @@ -74,7 +75,7 @@ private: std::any data; }; -class TimelineItem : public EventItemBase { +class QUOTIENT_API TimelineItem : public EventItemBase { public: // For compatibility with Qt containers, even though we use // a std:: container now for the room timeline @@ -103,7 +104,7 @@ inline const CallEventBase* EventItemBase::viewAs() const return evt->isCallEvent() ? weakPtrCast(evt) : nullptr; } -class PendingEventItem : public EventItemBase { +class QUOTIENT_API PendingEventItem : public EventItemBase { public: using EventItemBase::EventItemBase; diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h index 9cf77be3..c0f2202d 100644 --- a/lib/events/accountdataevents.h +++ b/lib/events/accountdataevents.h @@ -50,7 +50,7 @@ struct JsonObjectConverter { using TagsMap = QHash; #define DEFINE_SIMPLE_EVENT(_Name, _TypeId, _ContentType, _ContentKey) \ - class _Name : public Event { \ + class QUOTIENT_API _Name : public Event { \ public: \ using content_type = _ContentType; \ DEFINE_EVENT_TYPEID(_TypeId, _Name) \ diff --git a/lib/events/callanswerevent.h b/lib/events/callanswerevent.h index 4c01c941..8ffe60f2 100644 --- a/lib/events/callanswerevent.h +++ b/lib/events/callanswerevent.h @@ -7,7 +7,7 @@ #include "roomevent.h" namespace Quotient { -class CallAnswerEvent : public CallEventBase { +class QUOTIENT_API CallAnswerEvent : public CallEventBase { public: DEFINE_EVENT_TYPEID("m.call.answer", CallAnswerEvent) @@ -26,6 +26,5 @@ public: return contentPart("answer"_ls).value("sdp"_ls).toString(); } }; - REGISTER_EVENT_TYPE(CallAnswerEvent) } // namespace Quotient diff --git a/lib/events/callhangupevent.h b/lib/events/callhangupevent.h index f3f82833..b0017c59 100644 --- a/lib/events/callhangupevent.h +++ b/lib/events/callhangupevent.h @@ -7,7 +7,7 @@ #include "roomevent.h" namespace Quotient { -class CallHangupEvent : public CallEventBase { +class QUOTIENT_API CallHangupEvent : public CallEventBase { public: DEFINE_EVENT_TYPEID("m.call.hangup", CallHangupEvent) diff --git a/lib/events/callinviteevent.h b/lib/events/callinviteevent.h index 80b7d651..47362b5c 100644 --- a/lib/events/callinviteevent.h +++ b/lib/events/callinviteevent.h @@ -7,7 +7,7 @@ #include "roomevent.h" namespace Quotient { -class CallInviteEvent : public CallEventBase { +class QUOTIENT_API CallInviteEvent : public CallEventBase { public: DEFINE_EVENT_TYPEID("m.call.invite", CallInviteEvent) diff --git a/lib/events/directchatevent.h b/lib/events/directchatevent.h index e2143779..2018d3d6 100644 --- a/lib/events/directchatevent.h +++ b/lib/events/directchatevent.h @@ -6,7 +6,7 @@ #include "event.h" namespace Quotient { -class DirectChatEvent : public Event { +class QUOTIENT_API DirectChatEvent : public Event { public: DEFINE_EVENT_TYPEID("m.direct", DirectChatEvent) diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index de89a7c6..81343a29 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -25,7 +25,7 @@ namespace Quotient { * in general. It's possible, because RoomEvent interface is similar to Event's * one and doesn't add new restrictions, just provides additional features. */ -class EncryptedEvent : public RoomEvent { +class QUOTIENT_API EncryptedEvent : public RoomEvent { public: DEFINE_EVENT_TYPEID("m.room.encrypted", EncryptedEvent) diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h index 14439fcc..dfb28b2f 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -8,7 +8,7 @@ #include "stateevent.h" namespace Quotient { -class EncryptionEventContent : public EventContent::Base { +class QUOTIENT_API EncryptionEventContent : public EventContent::Base { public: enum EncryptionType : size_t { MegolmV1AesSha2 = 0, Undefined }; @@ -26,7 +26,7 @@ protected: using EncryptionType = EncryptionEventContent::EncryptionType; -class EncryptionEvent : public StateEvent { +class QUOTIENT_API EncryptionEvent : public StateEvent { Q_GADGET public: DEFINE_EVENT_TYPEID("m.room.encryption", EncryptionEvent) @@ -51,6 +51,5 @@ public: int rotationPeriodMs() const { return content().rotationPeriodMs; } int rotationPeriodMsgs() const { return content().rotationPeriodMsgs; } }; - REGISTER_EVENT_TYPE(EncryptionEvent) } // namespace Quotient diff --git a/lib/events/event.h b/lib/events/event.h index 8f62872d..8a0076d0 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -60,14 +60,14 @@ inline QJsonObject basicEventJson(const QString& matrixType, using event_type_t = size_t; using event_mtype_t = const char*; -class EventTypeRegistry { +class QUOTIENT_API EventTypeRegistry { public: ~EventTypeRegistry() = default; static event_type_t initializeTypeId(event_mtype_t matrixTypeId); template - static inline event_type_t initializeTypeId() + static event_type_t initializeTypeId() { return initializeTypeId(EventT::matrixTypeId()); } @@ -190,7 +190,7 @@ namespace _impl { // === Event === -class Event { +class QUOTIENT_API Event { public: using Type = event_type_t; static inline _impl::EventFactory factory { "Event" }; @@ -243,7 +243,7 @@ public: return fromJson(unsignedJson()[std::forward(key)]); } - friend QDebug operator<<(QDebug dbg, const Event& e) + friend QUOTIENT_API QDebug operator<<(QDebug dbg, const Event& e) { QDebugStateSaver _dss { dbg }; dbg.noquote().nospace() << e.matrixType() << '(' << e.type() << "): "; @@ -272,17 +272,20 @@ using Events = EventsArray; // This macro should be used in a public section of an event class to // provide matrixTypeId() and typeId(). -#define DEFINE_EVENT_TYPEID(_Id, _Type) \ - static constexpr event_mtype_t matrixTypeId() { return _Id; } \ - static auto typeId() { return Quotient::typeId<_Type>(); } \ +#define DEFINE_EVENT_TYPEID(_Id, _Type) \ + static QUOTIENT_EXPORT constexpr event_mtype_t matrixTypeId() \ + { \ + return _Id; \ + } \ + static QUOTIENT_EXPORT auto typeId() { return Quotient::typeId<_Type>(); } \ // End of macro // This macro should be put after an event class definition (in .h or .cpp) // to enable its deserialisation from a /sync and other // polymorphic event arrays -#define REGISTER_EVENT_TYPE(_Type) \ - [[maybe_unused]] inline const auto _factoryAdded##_Type = \ - _Type::factory.addMethod<_Type>(); \ +#define REGISTER_EVENT_TYPE(_Type) \ + [[maybe_unused]] QUOTIENT_API inline const auto _factoryAdded##_Type = \ + _Type::factory.addMethod<_Type>(); \ // End of macro // === Event loading === diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index f609a603..bfa7d926 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -6,14 +6,15 @@ // This file contains generic event content definitions, applicable to room // message events as well as other events (e.g., avatars). +#include "encryptedfile.h" +#include "quotient_export.h" + #include #include #include #include #include -#include "encryptedfile.h" - class QFileInfo; namespace Quotient { @@ -28,7 +29,7 @@ namespace EventContent { * assumed but not required that a content object can also be created * from plain data. */ - class Base { + class QUOTIENT_API Base { public: explicit Base(QJsonObject o = {}) : originalJson(std::move(o)) {} virtual ~Base() = default; @@ -76,7 +77,7 @@ namespace EventContent { * * This class is not polymorphic. */ - class FileInfo { + class QUOTIENT_API FileInfo { public: FileInfo() = default; explicit FileInfo(const QFileInfo& fi); @@ -121,7 +122,7 @@ namespace EventContent { /** * A content info class for image content types: image, thumbnail, video */ - class ImageInfo : public FileInfo { + class QUOTIENT_API ImageInfo : public FileInfo { public: ImageInfo() = default; explicit ImageInfo(const QFileInfo& fi, QSize imageSize = {}); @@ -146,7 +147,7 @@ namespace EventContent { * the JSON representation of event content; namely, * "info/thumbnail_url" and "info/thumbnail_info" fields are used. */ - class Thumbnail : public ImageInfo { + class QUOTIENT_API Thumbnail : public ImageInfo { public: Thumbnail() = default; // Allow empty thumbnails Thumbnail(const QJsonObject& infoJson, const Omittable &file = none); @@ -160,7 +161,7 @@ namespace EventContent { void fillInfoJson(QJsonObject* infoJson) const; }; - class TypedBase : public Base { + class QUOTIENT_API TypedBase : public Base { public: virtual QMimeType type() const = 0; virtual const FileInfo* fileInfo() const { return nullptr; } @@ -183,7 +184,7 @@ namespace EventContent { * \tparam InfoT base info class */ template - class UrlBasedContent : public TypedBase, public InfoT { + class QUOTIENT_API UrlBasedContent : public TypedBase, public InfoT { public: using InfoT::InfoT; explicit UrlBasedContent(const QJsonObject& json) @@ -215,7 +216,7 @@ namespace EventContent { }; template - class UrlWithThumbnailContent : public UrlBasedContent { + class QUOTIENT_API UrlWithThumbnailContent : public UrlBasedContent { public: // NB: when using inherited constructors, thumbnail has to be // initialised separately diff --git a/lib/events/reactionevent.h b/lib/events/reactionevent.h index 5a2b98c4..ce11eaed 100644 --- a/lib/events/reactionevent.h +++ b/lib/events/reactionevent.h @@ -7,7 +7,7 @@ namespace Quotient { -struct EventRelation { +struct QUOTIENT_API EventRelation { using reltypeid_t = const char*; static constexpr reltypeid_t Reply() { return "m.in_reply_to"; } static constexpr reltypeid_t Annotation() { return "m.annotation"; } @@ -31,12 +31,12 @@ struct EventRelation { } }; template <> -struct JsonObjectConverter { +struct QUOTIENT_API JsonObjectConverter { static void dumpTo(QJsonObject& jo, const EventRelation& pod); static void fillFrom(const QJsonObject& jo, EventRelation& pod); }; -class ReactionEvent : public RoomEvent { +class QUOTIENT_API ReactionEvent : public RoomEvent { public: DEFINE_EVENT_TYPEID("m.reaction", ReactionEvent) diff --git a/lib/events/receiptevent.h b/lib/events/receiptevent.h index 9683deef..5e077e47 100644 --- a/lib/events/receiptevent.h +++ b/lib/events/receiptevent.h @@ -19,7 +19,7 @@ struct ReceiptsForEvent { }; using EventsWithReceipts = QVector; -class ReceiptEvent : public Event { +class QUOTIENT_API ReceiptEvent : public Event { public: DEFINE_EVENT_TYPEID("m.receipt", ReceiptEvent) explicit ReceiptEvent(const EventsWithReceipts& ewrs); diff --git a/lib/events/roomavatarevent.h b/lib/events/roomavatarevent.h index 8618ba31..c54b5801 100644 --- a/lib/events/roomavatarevent.h +++ b/lib/events/roomavatarevent.h @@ -7,7 +7,8 @@ #include "stateevent.h" namespace Quotient { -class RoomAvatarEvent : public StateEvent { +class QUOTIENT_API RoomAvatarEvent + : public StateEvent { // It's a bit of an overkill to use a full-fledged ImageContent // because in reality m.room.avatar usually only has a single URL, // without a thumbnail. But The Spec says there be thumbnails, and diff --git a/lib/events/roomcreateevent.h b/lib/events/roomcreateevent.h index b3ad287c..016855b9 100644 --- a/lib/events/roomcreateevent.h +++ b/lib/events/roomcreateevent.h @@ -7,7 +7,7 @@ #include "quotient_common.h" namespace Quotient { -class RoomCreateEvent : public StateEventBase { +class QUOTIENT_API RoomCreateEvent : public StateEventBase { public: DEFINE_EVENT_TYPEID("m.room.create", RoomCreateEvent) diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 8be58481..3fbb247e 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -11,7 +11,7 @@ namespace Quotient { class RedactionEvent; /** This class corresponds to m.room.* events */ -class RoomEvent : public Event { +class QUOTIENT_API RoomEvent : public Event { public: static inline _impl::EventFactory factory { "RoomEvent" }; @@ -70,7 +70,7 @@ using RoomEventPtr = event_ptr_tt; using RoomEvents = EventsArray; using RoomEventsRange = Range; -class CallEventBase : public RoomEvent { +class QUOTIENT_API CallEventBase : public RoomEvent { public: CallEventBase(Type type, event_mtype_t matrixType, const QString& callId, int version, const QJsonObject& contentJson = {}); diff --git a/lib/events/roomkeyevent.h b/lib/events/roomkeyevent.h index d021fbec..c4df7936 100644 --- a/lib/events/roomkeyevent.h +++ b/lib/events/roomkeyevent.h @@ -6,7 +6,7 @@ #include "event.h" namespace Quotient { -class RoomKeyEvent : public Event +class QUOTIENT_API RoomKeyEvent : public Event { public: DEFINE_EVENT_TYPEID("m.room_key", RoomKeyEvent) diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h index 0fb464d4..5e446dbe 100644 --- a/lib/events/roommemberevent.h +++ b/lib/events/roommemberevent.h @@ -10,7 +10,7 @@ #include "quotient_common.h" namespace Quotient { -class MemberEventContent : public EventContent::Base { +class QUOTIENT_API MemberEventContent : public EventContent::Base { public: using MembershipType [[deprecated("Use Quotient::Membership instead")]] = Membership; @@ -33,7 +33,7 @@ protected: using MembershipType [[deprecated("Use Membership instead")]] = Membership; -class RoomMemberEvent : public StateEvent { +class QUOTIENT_API RoomMemberEvent : public StateEvent { Q_GADGET public: DEFINE_EVENT_TYPEID("m.room.member", RoomMemberEvent) @@ -95,6 +95,5 @@ doLoadEvent(const QJsonObject& json, const QString& matrixType) return makeEvent(json); return makeEvent(unknownEventTypeId(), json); } - REGISTER_EVENT_TYPE(RoomMemberEvent) } // namespace Quotient diff --git a/lib/events/roommessageevent.h b/lib/events/roommessageevent.h index 56597ddc..0c901b7a 100644 --- a/lib/events/roommessageevent.h +++ b/lib/events/roommessageevent.h @@ -16,7 +16,7 @@ namespace MessageEventContent = EventContent; // Back-compatibility /** * The event class corresponding to m.room.message events */ -class RoomMessageEvent : public RoomEvent { +class QUOTIENT_API RoomMessageEvent : public RoomEvent { Q_GADGET public: DEFINE_EVENT_TYPEID("m.room.message", RoomMessageEvent) @@ -120,7 +120,7 @@ namespace EventContent { * Available fields: mimeType, body. The body can be either rich text * or plain text, depending on what mimeType specifies. */ - class TextContent : public TypedBase { + class QUOTIENT_API TextContent : public TypedBase { public: TextContent(QString text, const QString& contentType, Omittable relatesTo = none); @@ -149,7 +149,7 @@ namespace EventContent { * - thumbnail.mimeType * - thumbnail.imageSize */ - class LocationContent : public TypedBase { + class QUOTIENT_API LocationContent : public TypedBase { public: LocationContent(const QString& geoUri, const Thumbnail& thumbnail = {}); explicit LocationContent(const QJsonObject& json); @@ -168,7 +168,7 @@ namespace EventContent { * A base class for info types that include duration: audio and video */ template - class PlayableContent : public ContentT { + class QUOTIENT_API PlayableContent : public ContentT { public: using ContentT::ContentT; PlayableContent(const QJsonObject& json) diff --git a/lib/events/roompowerlevelsevent.h b/lib/events/roompowerlevelsevent.h index 0346fc0d..80e27048 100644 --- a/lib/events/roompowerlevelsevent.h +++ b/lib/events/roompowerlevelsevent.h @@ -7,7 +7,7 @@ #include "stateevent.h" namespace Quotient { -class PowerLevelsEventContent : public EventContent::Base { +class QUOTIENT_API PowerLevelsEventContent : public EventContent::Base { public: struct Notifications { int room; @@ -34,7 +34,8 @@ protected: void fillJson(QJsonObject* o) const override; }; -class RoomPowerLevelsEvent : public StateEvent { +class QUOTIENT_API RoomPowerLevelsEvent + : public StateEvent { Q_GADGET public: DEFINE_EVENT_TYPEID("m.room.power_levels", RoomPowerLevelsEvent) @@ -61,9 +62,6 @@ public: int powerLevelForEvent(const QString& eventId) const; int powerLevelForState(const QString& eventId) const; int powerLevelForUser(const QString& userId) const; - -private: }; - REGISTER_EVENT_TYPE(RoomPowerLevelsEvent) } // namespace Quotient diff --git a/lib/events/roomtombstoneevent.h b/lib/events/roomtombstoneevent.h index 30e53738..e336c448 100644 --- a/lib/events/roomtombstoneevent.h +++ b/lib/events/roomtombstoneevent.h @@ -6,7 +6,7 @@ #include "stateevent.h" namespace Quotient { -class RoomTombstoneEvent : public StateEventBase { +class QUOTIENT_API RoomTombstoneEvent : public StateEventBase { public: DEFINE_EVENT_TYPEID("m.room.tombstone", RoomTombstoneEvent) diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index 9ce78609..d6557012 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -30,7 +30,8 @@ namespace EventContent { } // namespace EventContent #define DEFINE_SIMPLE_STATE_EVENT(_Name, _TypeId, _ValueType, _ContentKey) \ - class _Name : public StateEvent> { \ + class QUOTIENT_API _Name \ + : public StateEvent> { \ public: \ using value_type = content_type::value_type; \ DEFINE_EVENT_TYPEID(_TypeId, _Name) \ diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index c37965aa..6095d628 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -17,7 +17,7 @@ inline QJsonObject basicStateEventJson(const QString& matrixTypeId, { ContentKey, content } }; } -class StateEventBase : public RoomEvent { +class QUOTIENT_API StateEventBase : public RoomEvent { public: static inline _impl::EventFactory factory { "StateEvent" }; diff --git a/lib/events/stickerevent.h b/lib/events/stickerevent.h index 93671086..0957dca3 100644 --- a/lib/events/stickerevent.h +++ b/lib/events/stickerevent.h @@ -11,7 +11,7 @@ namespace Quotient { /// Sticker messages are specialised image messages that are displayed without /// controls (e.g. no "download" link, or light-box view on click, as would be /// displayed for for m.image events). -class StickerEvent : public RoomEvent +class QUOTIENT_API StickerEvent : public RoomEvent { public: DEFINE_EVENT_TYPEID("m.sticker", StickerEvent) diff --git a/lib/events/typingevent.h b/lib/events/typingevent.h index 7456100a..522f7e42 100644 --- a/lib/events/typingevent.h +++ b/lib/events/typingevent.h @@ -6,7 +6,7 @@ #include "event.h" namespace Quotient { -class TypingEvent : public Event { +class QUOTIENT_API TypingEvent : public Event { public: DEFINE_EVENT_TYPEID("m.typing", TypingEvent) diff --git a/lib/eventstats.h b/lib/eventstats.h index 77c661a7..a10c81fb 100644 --- a/lib/eventstats.h +++ b/lib/eventstats.h @@ -16,7 +16,7 @@ namespace Quotient { //! \note It's just a simple grouping of counters and is not automatically //! updated from the room as subsequent syncs arrive. //! \sa Room::unreadStats, Room::partiallyReadStats, Room::isEventNotable -struct EventStats { +struct QUOTIENT_API EventStats { Q_GADGET Q_PROPERTY(qsizetype notableCount MEMBER notableCount CONSTANT) Q_PROPERTY(qsizetype highlightCount MEMBER highlightCount CONSTANT) @@ -109,6 +109,6 @@ public: bool isValidFor(const Room* room, const marker_t& marker) const; }; -QDebug operator<<(QDebug dbg, const EventStats& es); +QUOTIENT_API QDebug operator<<(QDebug dbg, const EventStats& es); } diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index ddf243ed..f41fc63c 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -20,7 +20,7 @@ class ConnectionData; enum class HttpVerb { Get, Put, Post, Delete }; -class BaseJob : public QObject { +class QUOTIENT_API BaseJob : public QObject { Q_OBJECT Q_PROPERTY(QUrl requestUrl READ requestUrl CONSTANT) Q_PROPERTY(int maxRetries READ maxRetries WRITE setMaxRetries) @@ -470,7 +470,7 @@ private: QScopedPointer d; }; -inline bool isJobPending(BaseJob* job) +inline bool QUOTIENT_API isJobPending(BaseJob* job) { return job && job->error() == BaseJob::Pending; } diff --git a/lib/jobs/downloadfilejob.h b/lib/jobs/downloadfilejob.h index 0752af89..d9f3b686 100644 --- a/lib/jobs/downloadfilejob.h +++ b/lib/jobs/downloadfilejob.h @@ -6,7 +6,7 @@ #include "csapi/content-repo.h" namespace Quotient { -class DownloadFileJob : public GetContentJob { +class QUOTIENT_API DownloadFileJob : public GetContentJob { public: using GetContentJob::makeRequestUrl; static QUrl makeRequestUrl(QUrl baseUrl, const QUrl& mxcUri); diff --git a/lib/jobs/mediathumbnailjob.h b/lib/jobs/mediathumbnailjob.h index 3183feb1..c9f6da35 100644 --- a/lib/jobs/mediathumbnailjob.h +++ b/lib/jobs/mediathumbnailjob.h @@ -8,7 +8,7 @@ #include namespace Quotient { -class MediaThumbnailJob : public GetContentThumbnailJob { +class QUOTIENT_API MediaThumbnailJob : public GetContentThumbnailJob { public: using GetContentThumbnailJob::makeRequestUrl; static QUrl makeRequestUrl(QUrl baseUrl, const QUrl& mxcUri, diff --git a/lib/jobs/requestdata.h b/lib/jobs/requestdata.h index 4f05e5ff..41ad833a 100644 --- a/lib/jobs/requestdata.h +++ b/lib/jobs/requestdata.h @@ -3,6 +3,8 @@ #pragma once +#include "quotient_export.h" + #include #include @@ -19,7 +21,7 @@ namespace Quotient { * as well as JSON (and possibly other structures in the future) to * a QByteArray consumed by QNetworkAccessManager request methods. */ -class RequestData { +class QUOTIENT_API RequestData { public: RequestData(const QByteArray& a = {}); RequestData(const QJsonObject& jo); diff --git a/lib/mxcreply.h b/lib/mxcreply.h index efaf01c6..23049b7d 100644 --- a/lib/mxcreply.h +++ b/lib/mxcreply.h @@ -3,13 +3,15 @@ #pragma once +#include "quotient_export.h" + #include #include namespace Quotient { class Room; -class MxcReply : public QNetworkReply +class QUOTIENT_API MxcReply : public QNetworkReply { public: explicit MxcReply(); diff --git a/lib/networkaccessmanager.h b/lib/networkaccessmanager.h index 87bc12a1..d06f9736 100644 --- a/lib/networkaccessmanager.h +++ b/lib/networkaccessmanager.h @@ -3,6 +3,8 @@ #pragma once +#include "quotient_export.h" + #include #include @@ -10,7 +12,7 @@ namespace Quotient { class Room; class Connection; -class NetworkAccessManager : public QNetworkAccessManager { +class QUOTIENT_API NetworkAccessManager : public QNetworkAccessManager { Q_OBJECT public: NetworkAccessManager(QObject* parent = nullptr); diff --git a/lib/networksettings.h b/lib/networksettings.h index df11a9c8..c1446355 100644 --- a/lib/networksettings.h +++ b/lib/networksettings.h @@ -10,7 +10,7 @@ Q_DECLARE_METATYPE(QNetworkProxy::ProxyType) namespace Quotient { -class NetworkSettings : public SettingsGroup { +class QUOTIENT_API NetworkSettings : public SettingsGroup { Q_OBJECT QTNT_DECLARE_SETTING(QNetworkProxy::ProxyType, proxyType, setProxyType) QTNT_DECLARE_SETTING(QString, proxyHostName, setProxyHostName) diff --git a/lib/quotient_common.h b/lib/quotient_common.h index 0e3e2a40..a5926e8c 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -3,6 +3,8 @@ #pragma once +#include "quotient_export.h" + #include #include @@ -26,8 +28,21 @@ #define DECL_DEPRECATED_ENUMERATOR(Deprecated, Recommended) \ Deprecated Q_DECL_ENUMERATOR_DEPRECATED_X("Use " #Recommended) = Recommended +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) +// The first line is a usual way to indicate a namespace to moc; +// the second line redeclares the namespace static metaobject with +// QUOTIENT_API so that dynamically linked clients could serialise +// flag/enum values from the namespace. +#define QUO_NAMESPACE \ +Q_NAMESPACE \ +extern QUOTIENT_API const QMetaObject staticMetaObject; +#else +// Since Qt 5.14.0, it's all packed in a single macro +#define QUO_NAMESPACE Q_NAMESPACE_EXPORT(QUOTIENT_API) +#endif + namespace Quotient { -Q_NAMESPACE +QUO_NAMESPACE // std::array {} needs explicit template parameters on macOS because // Apple stdlib doesn't have deduction guides for std::array. C++20 has diff --git a/lib/room.h b/lib/room.h index 85c51a87..63a4aaea 100644 --- a/lib/room.h +++ b/lib/room.h @@ -45,7 +45,7 @@ class RedactEventJob; * This is specifically tuned to work with QML exposing all traits as * Q_PROPERTY values. */ -class FileTransferInfo { +class QUOTIENT_API FileTransferInfo { Q_GADGET Q_PROPERTY(bool isUpload MEMBER isUpload CONSTANT) Q_PROPERTY(bool active READ active CONSTANT) @@ -73,7 +73,7 @@ public: //! \brief Data structure for a room member's read receipt //! \sa Room::lastReadReceipt -class ReadReceipt { +class QUOTIENT_API ReadReceipt { Q_GADGET Q_PROPERTY(QString eventId MEMBER eventId CONSTANT) Q_PROPERTY(QDateTime timestamp MEMBER timestamp CONSTANT) @@ -110,7 +110,7 @@ private: Q_PROPERTY(Type type MEMBER type CONSTANT) }; -class Room : public QObject { +class QUOTIENT_API Room : public QObject { Q_OBJECT Q_PROPERTY(Connection* connection READ connection CONSTANT) Q_PROPERTY(User* localUser READ localUser CONSTANT) @@ -1037,7 +1037,7 @@ private: void setJoinState(JoinState state); }; -class MemberSorter { +class QUOTIENT_API MemberSorter { public: explicit MemberSorter(const Room* r) : room(r) {} diff --git a/lib/settings.h b/lib/settings.h index efd0d714..b66879c5 100644 --- a/lib/settings.h +++ b/lib/settings.h @@ -3,6 +3,8 @@ #pragma once +#include "quotient_export.h" + #include #include #include @@ -11,7 +13,7 @@ class QVariant; namespace Quotient { -class Settings : public QSettings { +class QUOTIENT_API Settings : public QSettings { Q_OBJECT public: /// Add a legacy organisation/application name to migrate settings from @@ -76,7 +78,7 @@ protected: QSettings legacySettings { legacyOrganizationName, legacyApplicationName }; }; -class SettingsGroup : public Settings { +class QUOTIENT_API SettingsGroup : public Settings { public: explicit SettingsGroup(QString path, QObject* parent = nullptr) : Settings(parent) @@ -124,7 +126,7 @@ private: setValue(QStringLiteral(qsettingname), std::move(newValue)); \ } -class AccountSettings : public SettingsGroup { +class QUOTIENT_API AccountSettings : public SettingsGroup { Q_OBJECT Q_PROPERTY(QString userId READ userId CONSTANT) QTNT_DECLARE_SETTING(QString, deviceId, setDeviceId) diff --git a/lib/ssosession.h b/lib/ssosession.h index 72dd60c4..a658c043 100644 --- a/lib/ssosession.h +++ b/lib/ssosession.h @@ -3,6 +3,8 @@ #pragma once +#include "quotient_export.h" + #include #include @@ -29,7 +31,7 @@ class Connection; * connection->prepareForSso(initialDeviceName)->ssoUrl()); * \endcode */ -class SsoSession : public QObject { +class QUOTIENT_API SsoSession : public QObject { Q_OBJECT Q_PROPERTY(QUrl ssoUrl READ ssoUrl CONSTANT) Q_PROPERTY(QUrl callbackUrl READ callbackUrl CONSTANT) diff --git a/lib/syncdata.h b/lib/syncdata.h index 36d2e0bf..e29540c2 100644 --- a/lib/syncdata.h +++ b/lib/syncdata.h @@ -22,7 +22,7 @@ constexpr auto HighlightCountKey = "highlight_count"_ls; * means that nothing has come from the server; heroes.value().isEmpty() * means a peculiar case of a room with the only member - the current user. */ -struct RoomSummary { +struct QUOTIENT_API RoomSummary { Omittable joinedMemberCount; Omittable invitedMemberCount; Omittable heroes; //< mxids of users to take part in the room diff --git a/lib/uri.h b/lib/uri.h index d8b892b6..78cd27c8 100644 --- a/lib/uri.h +++ b/lib/uri.h @@ -23,7 +23,7 @@ namespace Quotient { * its type, and obtain components, also in either unencoded (for displaying) * or encoded (for APIs) form. */ -class Uri : private QUrl { +class QUOTIENT_API Uri : private QUrl { Q_GADGET public: enum Type : char { diff --git a/lib/uriresolver.h b/lib/uriresolver.h index ff97324a..9140046c 100644 --- a/lib/uriresolver.h +++ b/lib/uriresolver.h @@ -25,7 +25,7 @@ class User; * gradual implementation. Derived classes are encouraged to override as many * of them as possible. */ -class UriResolverBase { +class QUOTIENT_API UriResolverBase { public: /*! \brief Resolve the resource and dispatch an action depending on its type * @@ -105,7 +105,7 @@ protected: * * \sa UriResolverBase, UriDispatcher */ -UriResolveResult +QUOTIENT_API UriResolveResult visitResource(Connection* account, const Uri& uri, std::function userHandler, std::function roomEventHandler, @@ -141,7 +141,7 @@ inline UriResolveResult checkResource(Connection* account, const Uri& uri) * synchronously - the returned value is the result of resolving the URI, * not acting on it. */ -class UriDispatcher : public QObject, public UriResolverBase { +class QUOTIENT_API UriDispatcher : public QObject, public UriResolverBase { Q_OBJECT public: explicit UriDispatcher(QObject* parent = nullptr) : QObject(parent) {} diff --git a/lib/user.h b/lib/user.h index 78b72bf2..435304ce 100644 --- a/lib/user.h +++ b/lib/user.h @@ -5,6 +5,7 @@ #pragma once #include "avatar.h" +#include "quotient_export.h" #include @@ -13,7 +14,7 @@ class Connection; class Room; class RoomMemberEvent; -class User : public QObject { +class QUOTIENT_API User : public QObject { Q_OBJECT Q_PROPERTY(QString id READ id CONSTANT) Q_PROPERTY(bool isGuest READ isGuest CONSTANT) diff --git a/lib/util.h b/lib/util.h index 97f0ecbc..399f93c2 100644 --- a/lib/util.h +++ b/lib/util.h @@ -4,6 +4,8 @@ #pragma once +#include "quotient_export.h" + #include #include @@ -244,26 +246,26 @@ inline std::pair findFirstOf(InputIt first, InputIt last, } /** Convert what looks like a URL or a Matrix ID to an HTML hyperlink */ -void linkifyUrls(QString& htmlEscapedText); +QUOTIENT_API void linkifyUrls(QString& htmlEscapedText); /** Sanitize the text before showing in HTML * * This does toHtmlEscaped() and removes Unicode BiDi marks. */ -QString sanitized(const QString& plainText); +QUOTIENT_API QString sanitized(const QString& plainText); /** Pretty-print plain text into HTML * * This includes HTML escaping of <,>,",& and calling linkifyUrls() */ -QString prettyPrint(const QString& plainText); +QUOTIENT_API QString prettyPrint(const QString& plainText); /** Return a path to cache directory after making sure that it exists * * The returned path has a trailing slash, clients don't need to append it. * \param dir path to cache directory relative to the standard cache path */ -QString cacheLocation(const QString& dirName); +QUOTIENT_API QString cacheLocation(const QString& dirName); /** Hue color component of based of the hash of the string. * @@ -272,13 +274,13 @@ QString cacheLocation(const QString& dirName); * Naming and range are the same as QColor's hueF method: * https://doc.qt.io/qt-5/qcolor.html#integer-vs-floating-point-precision */ -qreal stringToHueF(const QString& s); +QUOTIENT_API qreal stringToHueF(const QString& s); /** Extract the serverpart from MXID */ -QString serverPart(const QString& mxId); +QUOTIENT_API QString serverPart(const QString& mxId); -QString versionString(); -int majorVersion(); -int minorVersion(); -int patchVersion(); +QUOTIENT_API QString versionString(); +QUOTIENT_API int majorVersion(); +QUOTIENT_API int minorVersion(); +QUOTIENT_API int patchVersion(); } // namespace Quotient -- cgit v1.2.3 From f69e1c52246b986fb91f595ee0ee363e5343dbbb Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 29 Dec 2021 15:57:14 +0100 Subject: operation.h.mustache: Add QUOTIENT_API --- gtad/operation.h.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtad/operation.h.mustache b/gtad/operation.h.mustache index f91dc66c..063f0bbd 100644 --- a/gtad/operation.h.mustache +++ b/gtad/operation.h.mustache @@ -16,7 +16,7 @@ namespace Quotient { /*!{{>docCommentSummary}}{{#description}} * {{_}}{{/description}} */ -class {{camelCaseOperationId}}Job : public BaseJob { +class QUOTIENT_API {{camelCaseOperationId}}Job : public BaseJob { public: {{#models}} // Inner data structures -- cgit v1.2.3 From 952f8aa8ad19348b50a3b3545d98f7889bfdae76 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 29 Dec 2021 15:57:33 +0100 Subject: Regenerate CS API files --- lib/csapi/account-data.h | 8 ++++---- lib/csapi/admin.h | 2 +- lib/csapi/administrative_contact.h | 16 ++++++++-------- lib/csapi/appservice_room_directory.h | 3 ++- lib/csapi/banning.h | 4 ++-- lib/csapi/capabilities.h | 2 +- lib/csapi/content-repo.h | 12 ++++++------ lib/csapi/create_room.h | 2 +- lib/csapi/cross_signing.h | 4 ++-- lib/csapi/device_management.h | 10 +++++----- lib/csapi/directory.h | 8 ++++---- lib/csapi/event_context.h | 2 +- lib/csapi/filter.h | 4 ++-- lib/csapi/inviting.h | 2 +- lib/csapi/joining.h | 4 ++-- lib/csapi/keys.h | 8 ++++---- lib/csapi/kicking.h | 2 +- lib/csapi/knocking.h | 2 +- lib/csapi/leaving.h | 4 ++-- lib/csapi/list_joined_rooms.h | 2 +- lib/csapi/list_public_rooms.h | 8 ++++---- lib/csapi/login.h | 4 ++-- lib/csapi/logout.h | 4 ++-- lib/csapi/message_pagination.h | 2 +- lib/csapi/notifications.h | 2 +- lib/csapi/openid.h | 2 +- lib/csapi/peeking_events.h | 2 +- lib/csapi/presence.h | 4 ++-- lib/csapi/profile.h | 10 +++++----- lib/csapi/pusher.h | 4 ++-- lib/csapi/pushrules.h | 16 ++++++++-------- lib/csapi/read_markers.h | 2 +- lib/csapi/receipts.h | 2 +- lib/csapi/redaction.h | 2 +- lib/csapi/registration.h | 16 ++++++++-------- lib/csapi/report_content.h | 2 +- lib/csapi/room_send.h | 2 +- lib/csapi/room_state.h | 2 +- lib/csapi/room_upgrades.h | 2 +- lib/csapi/rooms.h | 10 +++++----- lib/csapi/search.h | 2 +- lib/csapi/sso_login_redirect.h | 4 ++-- lib/csapi/tags.h | 6 +++--- lib/csapi/third_party_lookup.h | 12 ++++++------ lib/csapi/third_party_membership.h | 2 +- lib/csapi/to_device.h | 2 +- lib/csapi/typing.h | 2 +- lib/csapi/users.h | 2 +- lib/csapi/versions.h | 2 +- lib/csapi/voip.h | 2 +- lib/csapi/wellknown.h | 2 +- lib/csapi/whoami.h | 2 +- 52 files changed, 120 insertions(+), 119 deletions(-) diff --git a/lib/csapi/account-data.h b/lib/csapi/account-data.h index 0c760e80..5140d340 100644 --- a/lib/csapi/account-data.h +++ b/lib/csapi/account-data.h @@ -14,7 +14,7 @@ namespace Quotient { * that set the account_data. The config will be synced to clients in the * top-level `account_data`. */ -class SetAccountDataJob : public BaseJob { +class QUOTIENT_API SetAccountDataJob : public BaseJob { public: /*! \brief Set some account_data for the user. * @@ -38,7 +38,7 @@ public: * Get some account_data for the client. This config is only visible to the user * that set the account_data. */ -class GetAccountDataJob : public BaseJob { +class QUOTIENT_API GetAccountDataJob : public BaseJob { public: /*! \brief Get some account_data for the user. * @@ -67,7 +67,7 @@ public: * visible to the user that set the account_data. The config will be synced to * clients in the per-room `account_data`. */ -class SetAccountDataPerRoomJob : public BaseJob { +class QUOTIENT_API SetAccountDataPerRoomJob : public BaseJob { public: /*! \brief Set some account_data for the user. * @@ -95,7 +95,7 @@ public: * Get some account_data for the client on a given room. This config is only * visible to the user that set the account_data. */ -class GetAccountDataPerRoomJob : public BaseJob { +class QUOTIENT_API GetAccountDataPerRoomJob : public BaseJob { public: /*! \brief Get some account_data for the user. * diff --git a/lib/csapi/admin.h b/lib/csapi/admin.h index 570bf24a..c53ddd7e 100644 --- a/lib/csapi/admin.h +++ b/lib/csapi/admin.h @@ -16,7 +16,7 @@ namespace Quotient { * up, or by a server admin. Server-local administrator privileges are not * specified in this document. */ -class GetWhoIsJob : public BaseJob { +class QUOTIENT_API GetWhoIsJob : public BaseJob { public: // Inner data structures diff --git a/lib/csapi/administrative_contact.h b/lib/csapi/administrative_contact.h index e436971d..e636b12a 100644 --- a/lib/csapi/administrative_contact.h +++ b/lib/csapi/administrative_contact.h @@ -24,7 +24,7 @@ namespace Quotient { * Identifiers in this list may be used by the homeserver as, for example, * identifiers that it will accept to reset the user's account password. */ -class GetAccount3PIDsJob : public BaseJob { +class QUOTIENT_API GetAccount3PIDsJob : public BaseJob { public: // Inner data structures @@ -102,7 +102,7 @@ struct JsonObjectConverter { * This results in this endpoint being an equivalent to `/3pid/bind` rather * than dual-purpose. */ -class Post3PIDsJob : public BaseJob { +class QUOTIENT_API Post3PIDsJob : public BaseJob { public: // Inner data structures @@ -154,7 +154,7 @@ struct JsonObjectConverter { * Homeservers should prevent the caller from adding a 3PID to their account if * it has already been added to another user's account on the homeserver. */ -class Add3PIDJob : public BaseJob { +class QUOTIENT_API Add3PIDJob : public BaseJob { public: /*! \brief Adds contact information to the user's account. * @@ -182,7 +182,7 @@ public: * * Homeservers should track successful binds so they can be unbound later. */ -class Bind3PIDJob : public BaseJob { +class QUOTIENT_API Bind3PIDJob : public BaseJob { public: /*! \brief Binds a 3PID to the user's account through an Identity Service. * @@ -211,7 +211,7 @@ public: * parameter because the homeserver is expected to sign the request to the * identity server instead. */ -class Delete3pidFromAccountJob : public BaseJob { +class QUOTIENT_API Delete3pidFromAccountJob : public BaseJob { public: /*! \brief Deletes a third party identifier from the user's account * @@ -254,7 +254,7 @@ public: * parameter because the homeserver is expected to sign the request to the * identity server instead. */ -class Unbind3pidFromAccountJob : public BaseJob { +class QUOTIENT_API Unbind3pidFromAccountJob : public BaseJob { public: /*! \brief Removes a user's third party identifier from an identity server. * @@ -300,7 +300,7 @@ public: * the email itself, either by sending a validation email itself or by using * a service it has control over. */ -class RequestTokenTo3PIDEmailJob : public BaseJob { +class QUOTIENT_API RequestTokenTo3PIDEmailJob : public BaseJob { public: /*! \brief Begins the validation process for an email address for * association with the user's account. @@ -342,7 +342,7 @@ public: * the phone number itself, either by sending a validation message itself or by * using a service it has control over. */ -class RequestTokenTo3PIDMSISDNJob : public BaseJob { +class QUOTIENT_API RequestTokenTo3PIDMSISDNJob : public BaseJob { public: /*! \brief Begins the validation process for a phone number for association * with the user's account. diff --git a/lib/csapi/appservice_room_directory.h b/lib/csapi/appservice_room_directory.h index 56a69592..6b2801ca 100644 --- a/lib/csapi/appservice_room_directory.h +++ b/lib/csapi/appservice_room_directory.h @@ -21,7 +21,8 @@ namespace Quotient { * instead of a typical client's access_token. This API cannot be invoked by * users who are not identified as application services. */ -class UpdateAppserviceRoomDirectoryVisibilityJob : public BaseJob { +class QUOTIENT_API UpdateAppserviceRoomDirectoryVisibilityJob + : public BaseJob { public: /*! \brief Updates a room's visibility in the application service's room * directory. diff --git a/lib/csapi/banning.h b/lib/csapi/banning.h index 7a9697d3..e4c60ce3 100644 --- a/lib/csapi/banning.h +++ b/lib/csapi/banning.h @@ -18,7 +18,7 @@ namespace Quotient { * The caller must have the required power level in order to perform this * operation. */ -class BanJob : public BaseJob { +class QUOTIENT_API BanJob : public BaseJob { public: /*! \brief Ban a user in the room. * @@ -46,7 +46,7 @@ public: * The caller must have the required power level in order to perform this * operation. */ -class UnbanJob : public BaseJob { +class QUOTIENT_API UnbanJob : public BaseJob { public: /*! \brief Unban a user from the room. * diff --git a/lib/csapi/capabilities.h b/lib/csapi/capabilities.h index da50c8c1..81b47cd4 100644 --- a/lib/csapi/capabilities.h +++ b/lib/csapi/capabilities.h @@ -13,7 +13,7 @@ namespace Quotient { * Gets information about the server's supported feature set * and other relevant capabilities. */ -class GetCapabilitiesJob : public BaseJob { +class QUOTIENT_API GetCapabilitiesJob : public BaseJob { public: // Inner data structures diff --git a/lib/csapi/content-repo.h b/lib/csapi/content-repo.h index 28409f5c..511db985 100644 --- a/lib/csapi/content-repo.h +++ b/lib/csapi/content-repo.h @@ -14,7 +14,7 @@ namespace Quotient { /*! \brief Upload some content to the content repository. * */ -class UploadContentJob : public BaseJob { +class QUOTIENT_API UploadContentJob : public BaseJob { public: /*! \brief Upload some content to the content repository. * @@ -40,7 +40,7 @@ public: /*! \brief Download content from the content repository. * */ -class GetContentJob : public BaseJob { +class QUOTIENT_API GetContentJob : public BaseJob { public: /*! \brief Download content from the content repository. * @@ -87,7 +87,7 @@ public: * the previous endpoint) but replace the target file name with the one * provided by the caller. */ -class GetContentOverrideNameJob : public BaseJob { +class QUOTIENT_API GetContentOverrideNameJob : public BaseJob { public: /*! \brief Download content from the content repository overriding the file * name @@ -142,7 +142,7 @@ public: * See the [Thumbnails](/client-server-api/#thumbnails) section for more * information. */ -class GetContentThumbnailJob : public BaseJob { +class QUOTIENT_API GetContentThumbnailJob : public BaseJob { public: /*! \brief Download a thumbnail of content from the content repository * @@ -204,7 +204,7 @@ public: * do not want to share with the homeserver, and this can mean that the URLs * being shared should also not be shared with the homeserver. */ -class GetUrlPreviewJob : public BaseJob { +class QUOTIENT_API GetUrlPreviewJob : public BaseJob { public: /*! \brief Get information about a URL for a client * @@ -252,7 +252,7 @@ public: * content repository APIs, for example, proxies may enforce a lower upload size * limit than is advertised by the server on this endpoint. */ -class GetConfigJob : public BaseJob { +class QUOTIENT_API GetConfigJob : public BaseJob { public: /// Get the configuration for the content repository. explicit GetConfigJob(); diff --git a/lib/csapi/create_room.h b/lib/csapi/create_room.h index 81dfbffc..7d566057 100644 --- a/lib/csapi/create_room.h +++ b/lib/csapi/create_room.h @@ -53,7 +53,7 @@ namespace Quotient { * requesting user as the creator, alongside other keys provided in the * `creation_content`. */ -class CreateRoomJob : public BaseJob { +class QUOTIENT_API CreateRoomJob : public BaseJob { public: // Inner data structures diff --git a/lib/csapi/cross_signing.h b/lib/csapi/cross_signing.h index 2ab65e06..617b61d1 100644 --- a/lib/csapi/cross_signing.h +++ b/lib/csapi/cross_signing.h @@ -17,7 +17,7 @@ namespace Quotient { * This API endpoint uses the [User-Interactive Authentication * API](/client-server-api/#user-interactive-authentication-api). */ -class UploadCrossSigningKeysJob : public BaseJob { +class QUOTIENT_API UploadCrossSigningKeysJob : public BaseJob { public: /*! \brief Upload cross-signing keys. * @@ -47,7 +47,7 @@ public: * Publishes cross-signing signatures for the user. The request body is a * map from user ID to key ID to signed JSON object. */ -class UploadCrossSigningSignaturesJob : public BaseJob { +class QUOTIENT_API UploadCrossSigningSignaturesJob : public BaseJob { public: /*! \brief Upload cross-signing signatures. * diff --git a/lib/csapi/device_management.h b/lib/csapi/device_management.h index 7fb69873..430d2132 100644 --- a/lib/csapi/device_management.h +++ b/lib/csapi/device_management.h @@ -15,7 +15,7 @@ namespace Quotient { * * Gets information about all devices for the current user. */ -class GetDevicesJob : public BaseJob { +class QUOTIENT_API GetDevicesJob : public BaseJob { public: /// List registered devices for the current user explicit GetDevicesJob(); @@ -40,7 +40,7 @@ public: * * Gets information on a single device, by device id. */ -class GetDeviceJob : public BaseJob { +class QUOTIENT_API GetDeviceJob : public BaseJob { public: /*! \brief Get a single device * @@ -66,7 +66,7 @@ public: * * Updates the metadata on the given device. */ -class UpdateDeviceJob : public BaseJob { +class QUOTIENT_API UpdateDeviceJob : public BaseJob { public: /*! \brief Update a device * @@ -88,7 +88,7 @@ public: * * Deletes the given device, and invalidates any access token associated with it. */ -class DeleteDeviceJob : public BaseJob { +class QUOTIENT_API DeleteDeviceJob : public BaseJob { public: /*! \brief Delete a device * @@ -111,7 +111,7 @@ public: * Deletes the given devices, and invalidates any access token associated with * them. */ -class DeleteDevicesJob : public BaseJob { +class QUOTIENT_API DeleteDevicesJob : public BaseJob { public: /*! \brief Bulk deletion of devices * diff --git a/lib/csapi/directory.h b/lib/csapi/directory.h index 93a31595..0bd13a76 100644 --- a/lib/csapi/directory.h +++ b/lib/csapi/directory.h @@ -11,7 +11,7 @@ namespace Quotient { /*! \brief Create a new mapping from room alias to room ID. * */ -class SetRoomAliasJob : public BaseJob { +class QUOTIENT_API SetRoomAliasJob : public BaseJob { public: /*! \brief Create a new mapping from room alias to room ID. * @@ -32,7 +32,7 @@ public: * domain part of the alias does not correspond to the server's own * domain. */ -class GetRoomIdByAliasJob : public BaseJob { +class QUOTIENT_API GetRoomIdByAliasJob : public BaseJob { public: /*! \brief Get the room ID corresponding to this room alias. * @@ -76,7 +76,7 @@ public: * return a successful response even if the user does not have permission to * update the `m.room.canonical_alias` event. */ -class DeleteRoomAliasJob : public BaseJob { +class QUOTIENT_API DeleteRoomAliasJob : public BaseJob { public: /*! \brief Remove a mapping of room alias to room ID. * @@ -112,7 +112,7 @@ public: * as they are not curated, unlike those listed in the `m.room.canonical_alias` * state event. */ -class GetLocalAliasesJob : public BaseJob { +class QUOTIENT_API GetLocalAliasesJob : public BaseJob { public: /*! \brief Get a list of local aliases on a given room. * diff --git a/lib/csapi/event_context.h b/lib/csapi/event_context.h index 4e50edf3..662b976b 100644 --- a/lib/csapi/event_context.h +++ b/lib/csapi/event_context.h @@ -19,7 +19,7 @@ namespace Quotient { * [Lazy-loading room members](/client-server-api/#lazy-loading-room-members) * for more information. */ -class GetEventContextJob : public BaseJob { +class QUOTIENT_API GetEventContextJob : public BaseJob { public: /*! \brief Get events and state around the specified event. * diff --git a/lib/csapi/filter.h b/lib/csapi/filter.h index 01bec36b..9518a461 100644 --- a/lib/csapi/filter.h +++ b/lib/csapi/filter.h @@ -16,7 +16,7 @@ namespace Quotient { * Returns a filter ID that may be used in future requests to * restrict which events are returned to the client. */ -class DefineFilterJob : public BaseJob { +class QUOTIENT_API DefineFilterJob : public BaseJob { public: /*! \brief Upload a new filter. * @@ -41,7 +41,7 @@ public: /*! \brief Download a filter * */ -class GetFilterJob : public BaseJob { +class QUOTIENT_API GetFilterJob : public BaseJob { public: /*! \brief Download a filter * diff --git a/lib/csapi/inviting.h b/lib/csapi/inviting.h index eb13cc95..21e6cb74 100644 --- a/lib/csapi/inviting.h +++ b/lib/csapi/inviting.h @@ -26,7 +26,7 @@ namespace Quotient { * If the user was invited to the room, the homeserver will append a * `m.room.member` event to the room. */ -class InviteUserJob : public BaseJob { +class QUOTIENT_API InviteUserJob : public BaseJob { public: /*! \brief Invite a user to participate in a particular room. * diff --git a/lib/csapi/joining.h b/lib/csapi/joining.h index d0199b11..f64152f7 100644 --- a/lib/csapi/joining.h +++ b/lib/csapi/joining.h @@ -25,7 +25,7 @@ namespace Quotient { * [`/initialSync`](/client-server-api/#get_matrixclientr0initialsync) and * [`/sync`](/client-server-api/#get_matrixclientr0sync) APIs. */ -class JoinRoomByIdJob : public BaseJob { +class QUOTIENT_API JoinRoomByIdJob : public BaseJob { public: /*! \brief Start the requesting user participating in a particular room. * @@ -67,7 +67,7 @@ public: * [`/initialSync`](/client-server-api/#get_matrixclientr0initialsync) and * [`/sync`](/client-server-api/#get_matrixclientr0sync) APIs. */ -class JoinRoomJob : public BaseJob { +class QUOTIENT_API JoinRoomJob : public BaseJob { public: /*! \brief Start the requesting user participating in a particular room. * diff --git a/lib/csapi/keys.h b/lib/csapi/keys.h index 7db09e8d..ce1ca9ed 100644 --- a/lib/csapi/keys.h +++ b/lib/csapi/keys.h @@ -15,7 +15,7 @@ namespace Quotient { * * Publishes end-to-end encryption keys for the device. */ -class UploadKeysJob : public BaseJob { +class QUOTIENT_API UploadKeysJob : public BaseJob { public: /*! \brief Upload end-to-end encryption keys. * @@ -48,7 +48,7 @@ public: * * Returns the current devices and identity keys for the given users. */ -class QueryKeysJob : public BaseJob { +class QUOTIENT_API QueryKeysJob : public BaseJob { public: // Inner data structures @@ -172,7 +172,7 @@ struct JsonObjectConverter { * * Claims one-time keys for use in pre-key messages. */ -class ClaimKeysJob : public BaseJob { +class QUOTIENT_API ClaimKeysJob : public BaseJob { public: /*! \brief Claim one-time encryption keys. * @@ -226,7 +226,7 @@ public: * * added new device identity keys or removed an existing device with * identity keys, between `from` and `to`. */ -class GetKeysChangesJob : public BaseJob { +class QUOTIENT_API GetKeysChangesJob : public BaseJob { public: /*! \brief Query users with recent device key updates. * diff --git a/lib/csapi/kicking.h b/lib/csapi/kicking.h index 11018368..6ac106e2 100644 --- a/lib/csapi/kicking.h +++ b/lib/csapi/kicking.h @@ -20,7 +20,7 @@ namespace Quotient { * directly adjust the target member's state by making a request to * `/rooms//state/m.room.member/`. */ -class KickJob : public BaseJob { +class QUOTIENT_API KickJob : public BaseJob { public: /*! \brief Kick a user from the room. * diff --git a/lib/csapi/knocking.h b/lib/csapi/knocking.h index 1108cb64..e3645b59 100644 --- a/lib/csapi/knocking.h +++ b/lib/csapi/knocking.h @@ -27,7 +27,7 @@ namespace Quotient { * The knock will appear as an entry in the response of the * [`/sync`](/client-server-api/#get_matrixclientr0sync) API. */ -class KnockRoomJob : public BaseJob { +class QUOTIENT_API KnockRoomJob : public BaseJob { public: /*! \brief Knock on a room, requesting permission to join. * diff --git a/lib/csapi/leaving.h b/lib/csapi/leaving.h index 2e402d16..19cac3f0 100644 --- a/lib/csapi/leaving.h +++ b/lib/csapi/leaving.h @@ -22,7 +22,7 @@ namespace Quotient { * The user will still be allowed to retrieve history from the room which * they were previously allowed to see. */ -class LeaveRoomJob : public BaseJob { +class QUOTIENT_API LeaveRoomJob : public BaseJob { public: /*! \brief Stop the requesting user participating in a particular room. * @@ -48,7 +48,7 @@ public: * If the user is currently joined to the room, they must leave the room * before calling this API. */ -class ForgetRoomJob : public BaseJob { +class QUOTIENT_API ForgetRoomJob : public BaseJob { public: /*! \brief Stop the requesting user remembering about a particular room. * diff --git a/lib/csapi/list_joined_rooms.h b/lib/csapi/list_joined_rooms.h index 59a24a49..aea68afd 100644 --- a/lib/csapi/list_joined_rooms.h +++ b/lib/csapi/list_joined_rooms.h @@ -12,7 +12,7 @@ namespace Quotient { * * This API returns a list of the user's current rooms. */ -class GetJoinedRoomsJob : public BaseJob { +class QUOTIENT_API GetJoinedRoomsJob : public BaseJob { public: /// Lists the user's current rooms. explicit GetJoinedRoomsJob(); diff --git a/lib/csapi/list_public_rooms.h b/lib/csapi/list_public_rooms.h index 963c8b56..e1f03db7 100644 --- a/lib/csapi/list_public_rooms.h +++ b/lib/csapi/list_public_rooms.h @@ -14,7 +14,7 @@ namespace Quotient { * * Gets the visibility of a given room on the server's public room directory. */ -class GetRoomVisibilityOnDirectoryJob : public BaseJob { +class QUOTIENT_API GetRoomVisibilityOnDirectoryJob : public BaseJob { public: /*! \brief Gets the visibility of a room in the directory * @@ -48,7 +48,7 @@ public: * here, for instance that room visibility can only be changed by * the room creator or a server administrator. */ -class SetRoomVisibilityOnDirectoryJob : public BaseJob { +class QUOTIENT_API SetRoomVisibilityOnDirectoryJob : public BaseJob { public: /*! \brief Sets the visibility of a room in the room directory * @@ -70,7 +70,7 @@ public: * This API returns paginated responses. The rooms are ordered by the number * of joined members, with the largest rooms first. */ -class GetPublicRoomsJob : public BaseJob { +class QUOTIENT_API GetPublicRoomsJob : public BaseJob { public: /*! \brief Lists the public rooms on the server. * @@ -133,7 +133,7 @@ public: * This API returns paginated responses. The rooms are ordered by the number * of joined members, with the largest rooms first. */ -class QueryPublicRoomsJob : public BaseJob { +class QUOTIENT_API QueryPublicRoomsJob : public BaseJob { public: // Inner data structures diff --git a/lib/csapi/login.h b/lib/csapi/login.h index b35db1eb..ce6951eb 100644 --- a/lib/csapi/login.h +++ b/lib/csapi/login.h @@ -16,7 +16,7 @@ namespace Quotient { * Gets the homeserver's supported login types to authenticate users. Clients * should pick one of these and supply it as the `type` when logging in. */ -class GetLoginFlowsJob : public BaseJob { +class QUOTIENT_API GetLoginFlowsJob : public BaseJob { public: // Inner data structures @@ -73,7 +73,7 @@ struct JsonObjectConverter { * [Relationship between access tokens and * devices](/client-server-api/#relationship-between-access-tokens-and-devices). */ -class LoginJob : public BaseJob { +class QUOTIENT_API LoginJob : public BaseJob { public: /*! \brief Authenticates the user. * diff --git a/lib/csapi/logout.h b/lib/csapi/logout.h index 2e4c2692..3f1ac7fa 100644 --- a/lib/csapi/logout.h +++ b/lib/csapi/logout.h @@ -15,7 +15,7 @@ namespace Quotient { * [Device keys](/client-server-api/#device-keys) for the device are deleted * alongside the device. */ -class LogoutJob : public BaseJob { +class QUOTIENT_API LogoutJob : public BaseJob { public: /// Invalidates a user access token explicit LogoutJob(); @@ -44,7 +44,7 @@ public: * used in the request, and therefore the attacker is unable to take over the * account in this way. */ -class LogoutAllJob : public BaseJob { +class QUOTIENT_API LogoutAllJob : public BaseJob { public: /// Invalidates all access tokens for a user explicit LogoutAllJob(); diff --git a/lib/csapi/message_pagination.h b/lib/csapi/message_pagination.h index 363e4d99..8c18f104 100644 --- a/lib/csapi/message_pagination.h +++ b/lib/csapi/message_pagination.h @@ -18,7 +18,7 @@ namespace Quotient { * [Lazy-loading room members](/client-server-api/#lazy-loading-room-members) * for more information. */ -class GetRoomEventsJob : public BaseJob { +class QUOTIENT_API GetRoomEventsJob : public BaseJob { public: /*! \brief Get a list of events for this room * diff --git a/lib/csapi/notifications.h b/lib/csapi/notifications.h index 0c38fe6b..23211758 100644 --- a/lib/csapi/notifications.h +++ b/lib/csapi/notifications.h @@ -14,7 +14,7 @@ namespace Quotient { * This API is used to paginate through the list of events that the * user has been, or would have been notified about. */ -class GetNotificationsJob : public BaseJob { +class QUOTIENT_API GetNotificationsJob : public BaseJob { public: // Inner data structures diff --git a/lib/csapi/openid.h b/lib/csapi/openid.h index 0be39c8c..773b6011 100644 --- a/lib/csapi/openid.h +++ b/lib/csapi/openid.h @@ -21,7 +21,7 @@ namespace Quotient { * be used to request another OpenID access token or call `/sync`, for * example. */ -class RequestOpenIdTokenJob : public BaseJob { +class QUOTIENT_API RequestOpenIdTokenJob : public BaseJob { public: /*! \brief Get an OpenID token object to verify the requester's identity. * diff --git a/lib/csapi/peeking_events.h b/lib/csapi/peeking_events.h index 885ff340..14cb6f0b 100644 --- a/lib/csapi/peeking_events.h +++ b/lib/csapi/peeking_events.h @@ -22,7 +22,7 @@ namespace Quotient { * API will also be deprecated at some point, but its replacement is not * yet known. */ -class PeekEventsJob : public BaseJob { +class QUOTIENT_API PeekEventsJob : public BaseJob { public: /*! \brief Listen on the event stream. * diff --git a/lib/csapi/presence.h b/lib/csapi/presence.h index 4ab50e25..52445205 100644 --- a/lib/csapi/presence.h +++ b/lib/csapi/presence.h @@ -15,7 +15,7 @@ namespace Quotient { * not need to specify the `last_active_ago` field. You cannot set the * presence state of another user. */ -class SetPresenceJob : public BaseJob { +class QUOTIENT_API SetPresenceJob : public BaseJob { public: /*! \brief Update this user's presence state. * @@ -36,7 +36,7 @@ public: * * Get the given user's presence state. */ -class GetPresenceJob : public BaseJob { +class QUOTIENT_API GetPresenceJob : public BaseJob { public: /*! \brief Get this user's presence state. * diff --git a/lib/csapi/profile.h b/lib/csapi/profile.h index 7f9c9e95..b00c944b 100644 --- a/lib/csapi/profile.h +++ b/lib/csapi/profile.h @@ -13,7 +13,7 @@ namespace Quotient { * This API sets the given user's display name. You must have permission to * set this user's display name, e.g. you need to have their `access_token`. */ -class SetDisplayNameJob : public BaseJob { +class QUOTIENT_API SetDisplayNameJob : public BaseJob { public: /*! \brief Set the user's display name. * @@ -33,7 +33,7 @@ public: * own displayname or to query the name of other users; either locally or * on remote homeservers. */ -class GetDisplayNameJob : public BaseJob { +class QUOTIENT_API GetDisplayNameJob : public BaseJob { public: /*! \brief Get the user's display name. * @@ -63,7 +63,7 @@ public: * This API sets the given user's avatar URL. You must have permission to * set this user's avatar URL, e.g. you need to have their `access_token`. */ -class SetAvatarUrlJob : public BaseJob { +class QUOTIENT_API SetAvatarUrlJob : public BaseJob { public: /*! \brief Set the user's avatar URL. * @@ -82,7 +82,7 @@ public: * own avatar URL or to query the URL of other users; either locally or * on remote homeservers. */ -class GetAvatarUrlJob : public BaseJob { +class QUOTIENT_API GetAvatarUrlJob : public BaseJob { public: /*! \brief Get the user's avatar URL. * @@ -111,7 +111,7 @@ public: * locally or on remote homeservers. This API may return keys which are not * limited to `displayname` or `avatar_url`. */ -class GetUserProfileJob : public BaseJob { +class QUOTIENT_API GetUserProfileJob : public BaseJob { public: /*! \brief Get this user's profile information. * diff --git a/lib/csapi/pusher.h b/lib/csapi/pusher.h index 622b0df6..d859ffc4 100644 --- a/lib/csapi/pusher.h +++ b/lib/csapi/pusher.h @@ -12,7 +12,7 @@ namespace Quotient { * * Gets all currently active pushers for the authenticated user. */ -class GetPushersJob : public BaseJob { +class QUOTIENT_API GetPushersJob : public BaseJob { public: // Inner data structures @@ -108,7 +108,7 @@ struct JsonObjectConverter { * [pushers](/client-server-api/#push-notifications) for this user ID. The * behaviour of this endpoint varies depending on the values in the JSON body. */ -class PostPusherJob : public BaseJob { +class QUOTIENT_API PostPusherJob : public BaseJob { public: // Inner data structures diff --git a/lib/csapi/pushrules.h b/lib/csapi/pushrules.h index a5eb48f0..d6c57efd 100644 --- a/lib/csapi/pushrules.h +++ b/lib/csapi/pushrules.h @@ -19,7 +19,7 @@ namespace Quotient { * `/pushrules/global/`. This will return a subset of this data under the * specified key e.g. the `global` key. */ -class GetPushRulesJob : public BaseJob { +class QUOTIENT_API GetPushRulesJob : public BaseJob { public: /// Retrieve all push rulesets. explicit GetPushRulesJob(); @@ -44,7 +44,7 @@ public: * * Retrieve a single specified push rule. */ -class GetPushRuleJob : public BaseJob { +class QUOTIENT_API GetPushRuleJob : public BaseJob { public: /*! \brief Retrieve a push rule. * @@ -79,7 +79,7 @@ public: * * This endpoint removes the push rule defined in the path. */ -class DeletePushRuleJob : public BaseJob { +class QUOTIENT_API DeletePushRuleJob : public BaseJob { public: /*! \brief Delete a push rule. * @@ -112,7 +112,7 @@ public: * * When creating push rules, they MUST be enabled by default. */ -class SetPushRuleJob : public BaseJob { +class QUOTIENT_API SetPushRuleJob : public BaseJob { public: /*! \brief Add or change a push rule. * @@ -160,7 +160,7 @@ public: * * This endpoint gets whether the specified push rule is enabled. */ -class IsPushRuleEnabledJob : public BaseJob { +class QUOTIENT_API IsPushRuleEnabledJob : public BaseJob { public: /*! \brief Get whether a push rule is enabled * @@ -195,7 +195,7 @@ public: * * This endpoint allows clients to enable or disable the specified push rule. */ -class SetPushRuleEnabledJob : public BaseJob { +class QUOTIENT_API SetPushRuleEnabledJob : public BaseJob { public: /*! \brief Enable or disable a push rule. * @@ -219,7 +219,7 @@ public: * * This endpoint get the actions for the specified push rule. */ -class GetPushRuleActionsJob : public BaseJob { +class QUOTIENT_API GetPushRuleActionsJob : public BaseJob { public: /*! \brief The actions for a push rule * @@ -258,7 +258,7 @@ public: * This endpoint allows clients to change the actions of a push rule. * This can be used to change the actions of builtin rules. */ -class SetPushRuleActionsJob : public BaseJob { +class QUOTIENT_API SetPushRuleActionsJob : public BaseJob { public: /*! \brief Set the actions for a push rule. * diff --git a/lib/csapi/read_markers.h b/lib/csapi/read_markers.h index 00a2aa0d..d13fa4fc 100644 --- a/lib/csapi/read_markers.h +++ b/lib/csapi/read_markers.h @@ -13,7 +13,7 @@ namespace Quotient { * Sets the position of the read marker for a given room, and optionally * the read receipt's location. */ -class SetReadMarkerJob : public BaseJob { +class QUOTIENT_API SetReadMarkerJob : public BaseJob { public: /*! \brief Set the position of the read marker for a room. * diff --git a/lib/csapi/receipts.h b/lib/csapi/receipts.h index 7ac093cd..e29e7b29 100644 --- a/lib/csapi/receipts.h +++ b/lib/csapi/receipts.h @@ -13,7 +13,7 @@ namespace Quotient { * This API updates the marker for the given receipt type to the event ID * specified. */ -class PostReceiptJob : public BaseJob { +class QUOTIENT_API PostReceiptJob : public BaseJob { public: /*! \brief Send a receipt for the given event ID. * diff --git a/lib/csapi/redaction.h b/lib/csapi/redaction.h index f0db9f9f..29d9c5d5 100644 --- a/lib/csapi/redaction.h +++ b/lib/csapi/redaction.h @@ -22,7 +22,7 @@ namespace Quotient { * * Server administrators may redact events sent by users on their server. */ -class RedactEventJob : public BaseJob { +class QUOTIENT_API RedactEventJob : public BaseJob { public: /*! \brief Strips all non-integrity-critical information out of an event. * diff --git a/lib/csapi/registration.h b/lib/csapi/registration.h index c1614f20..10375971 100644 --- a/lib/csapi/registration.h +++ b/lib/csapi/registration.h @@ -59,7 +59,7 @@ namespace Quotient { * Any user ID returned by this API must conform to the grammar given in the * [Matrix specification](/appendices/#user-identifiers). */ -class RegisterJob : public BaseJob { +class QUOTIENT_API RegisterJob : public BaseJob { public: /*! \brief Register for an account on this homeserver. * @@ -143,7 +143,7 @@ public: * should validate the email itself, either by sending a validation email * itself or by using a service it has control over. */ -class RequestTokenToRegisterEmailJob : public BaseJob { +class QUOTIENT_API RequestTokenToRegisterEmailJob : public BaseJob { public: /*! \brief Begins the validation process for an email to be used during * registration. @@ -175,7 +175,7 @@ public: * should validate the phone number itself, either by sending a validation * message itself or by using a service it has control over. */ -class RequestTokenToRegisterMSISDNJob : public BaseJob { +class QUOTIENT_API RequestTokenToRegisterMSISDNJob : public BaseJob { public: /*! \brief Requests a validation token be sent to the given phone number for * the purpose of registering an account @@ -215,7 +215,7 @@ public: * access token provided in the request. Whether other access tokens for * the user are revoked depends on the request parameters. */ -class ChangePasswordJob : public BaseJob { +class QUOTIENT_API ChangePasswordJob : public BaseJob { public: /*! \brief Changes a user's password. * @@ -257,7 +257,7 @@ public: * The homeserver should validate the email itself, either by sending a * validation email itself or by using a service it has control over. */ -class RequestTokenToResetPasswordEmailJob : public BaseJob { +class QUOTIENT_API RequestTokenToResetPasswordEmailJob : public BaseJob { public: /*! \brief Requests a validation token be sent to the given email address * for the purpose of resetting a user's password @@ -309,7 +309,7 @@ public: * The homeserver should validate the phone number itself, either by sending a * validation message itself or by using a service it has control over. */ -class RequestTokenToResetPasswordMSISDNJob : public BaseJob { +class QUOTIENT_API RequestTokenToResetPasswordMSISDNJob : public BaseJob { public: /*! \brief Requests a validation token be sent to the given phone number for * the purpose of resetting a user's password. @@ -361,7 +361,7 @@ public: * parameter because the homeserver is expected to sign the request to the * identity server instead. */ -class DeactivateAccountJob : public BaseJob { +class QUOTIENT_API DeactivateAccountJob : public BaseJob { public: /*! \brief Deactivate a user's account. * @@ -411,7 +411,7 @@ public: * reserve the username. This can mean that the username becomes unavailable * between checking its availability and attempting to register it. */ -class CheckUsernameAvailabilityJob : public BaseJob { +class QUOTIENT_API CheckUsernameAvailabilityJob : public BaseJob { public: /*! \brief Checks to see if a username is available on the server. * diff --git a/lib/csapi/report_content.h b/lib/csapi/report_content.h index e401c2e1..8c533c19 100644 --- a/lib/csapi/report_content.h +++ b/lib/csapi/report_content.h @@ -13,7 +13,7 @@ namespace Quotient { * Reports an event as inappropriate to the server, which may then notify * the appropriate people. */ -class ReportContentJob : public BaseJob { +class QUOTIENT_API ReportContentJob : public BaseJob { public: /*! \brief Reports an event as inappropriate. * diff --git a/lib/csapi/room_send.h b/lib/csapi/room_send.h index 96f5beca..fea3d59d 100644 --- a/lib/csapi/room_send.h +++ b/lib/csapi/room_send.h @@ -18,7 +18,7 @@ namespace Quotient { * fields in this object will vary depending on the type of event. See * [Room Events](/client-server-api/#room-events) for the m. event specification. */ -class SendMessageJob : public BaseJob { +class QUOTIENT_API SendMessageJob : public BaseJob { public: /*! \brief Send a message event to the given room. * diff --git a/lib/csapi/room_state.h b/lib/csapi/room_state.h index f95af223..a00b0947 100644 --- a/lib/csapi/room_state.h +++ b/lib/csapi/room_state.h @@ -29,7 +29,7 @@ namespace Quotient { * state event is to be sent. Servers do not validate aliases which are * being removed or are already present in the state event. */ -class SetRoomStateWithKeyJob : public BaseJob { +class QUOTIENT_API SetRoomStateWithKeyJob : public BaseJob { public: /*! \brief Send a state event to the given room. * diff --git a/lib/csapi/room_upgrades.h b/lib/csapi/room_upgrades.h index 58327587..0432f667 100644 --- a/lib/csapi/room_upgrades.h +++ b/lib/csapi/room_upgrades.h @@ -12,7 +12,7 @@ namespace Quotient { * * Upgrades the given room to a particular room version. */ -class UpgradeRoomJob : public BaseJob { +class QUOTIENT_API UpgradeRoomJob : public BaseJob { public: /*! \brief Upgrades a room to a new room version. * diff --git a/lib/csapi/rooms.h b/lib/csapi/rooms.h index 2620582b..f0815109 100644 --- a/lib/csapi/rooms.h +++ b/lib/csapi/rooms.h @@ -15,7 +15,7 @@ namespace Quotient { * Get a single event based on `roomId/eventId`. You must have permission to * retrieve this event e.g. by being a member in the room for this event. */ -class GetOneRoomEventJob : public BaseJob { +class QUOTIENT_API GetOneRoomEventJob : public BaseJob { public: /*! \brief Get a single event by event ID. * @@ -48,7 +48,7 @@ public: * state of the room. If the user has left the room then the state is * taken from the state of the room when they left. */ -class GetRoomStateWithKeyJob : public BaseJob { +class QUOTIENT_API GetRoomStateWithKeyJob : public BaseJob { public: /*! \brief Get the state identified by the type and key. * @@ -80,7 +80,7 @@ public: * * Get the state events for the current state of a room. */ -class GetRoomStateJob : public BaseJob { +class QUOTIENT_API GetRoomStateJob : public BaseJob { public: /*! \brief Get all state events in the current state of a room. * @@ -106,7 +106,7 @@ public: * * Get the list of members for this room. */ -class GetMembersByRoomJob : public BaseJob { +class QUOTIENT_API GetMembersByRoomJob : public BaseJob { public: /*! \brief Get the m.room.member events for the room. * @@ -161,7 +161,7 @@ public: * respond than `/members` as it can be implemented more efficiently on the * server. */ -class GetJoinedMembersByRoomJob : public BaseJob { +class QUOTIENT_API GetJoinedMembersByRoomJob : public BaseJob { public: // Inner data structures diff --git a/lib/csapi/search.h b/lib/csapi/search.h index 3d02752a..8683413d 100644 --- a/lib/csapi/search.h +++ b/lib/csapi/search.h @@ -15,7 +15,7 @@ namespace Quotient { * * Performs a full text search across different categories. */ -class SearchJob : public BaseJob { +class QUOTIENT_API SearchJob : public BaseJob { public: // Inner data structures diff --git a/lib/csapi/sso_login_redirect.h b/lib/csapi/sso_login_redirect.h index ade1eb7d..f4f81c1e 100644 --- a/lib/csapi/sso_login_redirect.h +++ b/lib/csapi/sso_login_redirect.h @@ -17,7 +17,7 @@ namespace Quotient { * or present a page which lets the user select an IdP to continue * with in the event multiple are supported by the server. */ -class RedirectToSSOJob : public BaseJob { +class QUOTIENT_API RedirectToSSOJob : public BaseJob { public: /*! \brief Redirect the user's browser to the SSO interface. * @@ -44,7 +44,7 @@ public: * The server MUST respond with an HTTP redirect to the SSO interface * for that IdP. */ -class RedirectToIdPJob : public BaseJob { +class QUOTIENT_API RedirectToIdPJob : public BaseJob { public: /*! \brief Redirect the user's browser to the SSO interface for an IdP. * diff --git a/lib/csapi/tags.h b/lib/csapi/tags.h index a854531a..f4250674 100644 --- a/lib/csapi/tags.h +++ b/lib/csapi/tags.h @@ -12,7 +12,7 @@ namespace Quotient { * * List the tags set by a user on a room. */ -class GetRoomTagsJob : public BaseJob { +class QUOTIENT_API GetRoomTagsJob : public BaseJob { public: // Inner data structures @@ -68,7 +68,7 @@ struct JsonObjectConverter { * * Add a tag to the room. */ -class SetRoomTagJob : public BaseJob { +class QUOTIENT_API SetRoomTagJob : public BaseJob { public: /*! \brief Add a tag to a room. * @@ -98,7 +98,7 @@ public: * * Remove a tag from the room. */ -class DeleteRoomTagJob : public BaseJob { +class QUOTIENT_API DeleteRoomTagJob : public BaseJob { public: /*! \brief Remove a tag from the room. * diff --git a/lib/csapi/third_party_lookup.h b/lib/csapi/third_party_lookup.h index 969e767c..30c5346e 100644 --- a/lib/csapi/third_party_lookup.h +++ b/lib/csapi/third_party_lookup.h @@ -18,7 +18,7 @@ namespace Quotient { * homeserver. Includes both the available protocols and all fields * required for queries against each protocol. */ -class GetProtocolsJob : public BaseJob { +class QUOTIENT_API GetProtocolsJob : public BaseJob { public: /// Retrieve metadata about all protocols that a homeserver supports. explicit GetProtocolsJob(); @@ -45,7 +45,7 @@ public: * Fetches the metadata from the homeserver about a particular third party * protocol. */ -class GetProtocolMetadataJob : public BaseJob { +class QUOTIENT_API GetProtocolMetadataJob : public BaseJob { public: /*! \brief Retrieve metadata about a specific protocol that the homeserver * supports. @@ -82,7 +82,7 @@ public: * identifier. It should attempt to canonicalise the identifier as much * as reasonably possible given the network type. */ -class QueryLocationByProtocolJob : public BaseJob { +class QUOTIENT_API QueryLocationByProtocolJob : public BaseJob { public: /*! \brief Retrieve Matrix-side portals rooms leading to a third party * location. @@ -119,7 +119,7 @@ public: * Retrieve a Matrix User ID linked to a user on the third party service, given * a set of user parameters. */ -class QueryUserByProtocolJob : public BaseJob { +class QUOTIENT_API QueryUserByProtocolJob : public BaseJob { public: /*! \brief Retrieve the Matrix User ID of a corresponding third party user. * @@ -155,7 +155,7 @@ public: * Retrieve an array of third party network locations from a Matrix room * alias. */ -class QueryLocationByAliasJob : public BaseJob { +class QUOTIENT_API QueryLocationByAliasJob : public BaseJob { public: /*! \brief Reverse-lookup third party locations given a Matrix room alias. * @@ -184,7 +184,7 @@ public: * * Retrieve an array of third party users from a Matrix User ID. */ -class QueryUserByIDJob : public BaseJob { +class QUOTIENT_API QueryUserByIDJob : public BaseJob { public: /*! \brief Reverse-lookup third party users given a Matrix User ID. * diff --git a/lib/csapi/third_party_membership.h b/lib/csapi/third_party_membership.h index a424678f..1edb969e 100644 --- a/lib/csapi/third_party_membership.h +++ b/lib/csapi/third_party_membership.h @@ -52,7 +52,7 @@ namespace Quotient { * If a token is requested from the identity server, the homeserver will * append a `m.room.third_party_invite` event to the room. */ -class InviteBy3PIDJob : public BaseJob { +class QUOTIENT_API InviteBy3PIDJob : public BaseJob { public: /*! \brief Invite a user to participate in a particular room. * diff --git a/lib/csapi/to_device.h b/lib/csapi/to_device.h index 7a237195..5b6e0bfb 100644 --- a/lib/csapi/to_device.h +++ b/lib/csapi/to_device.h @@ -13,7 +13,7 @@ namespace Quotient { * This endpoint is used to send send-to-device events to a set of * client devices. */ -class SendToDeviceJob : public BaseJob { +class QUOTIENT_API SendToDeviceJob : public BaseJob { public: /*! \brief Send an event to a given set of devices. * diff --git a/lib/csapi/typing.h b/lib/csapi/typing.h index 64a310d0..234e91b0 100644 --- a/lib/csapi/typing.h +++ b/lib/csapi/typing.h @@ -15,7 +15,7 @@ namespace Quotient { * Alternatively, if `typing` is `false`, it tells the server that the * user has stopped typing. */ -class SetTypingJob : public BaseJob { +class QUOTIENT_API SetTypingJob : public BaseJob { public: /*! \brief Informs the server that the user has started or stopped typing. * diff --git a/lib/csapi/users.h b/lib/csapi/users.h index ec186592..3c99758b 100644 --- a/lib/csapi/users.h +++ b/lib/csapi/users.h @@ -21,7 +21,7 @@ namespace Quotient { * names preferably using a collation determined based upon the * `Accept-Language` header provided in the request, if present. */ -class SearchUserDirectoryJob : public BaseJob { +class QUOTIENT_API SearchUserDirectoryJob : public BaseJob { public: // Inner data structures diff --git a/lib/csapi/versions.h b/lib/csapi/versions.h index 896e2ea9..4445dbd2 100644 --- a/lib/csapi/versions.h +++ b/lib/csapi/versions.h @@ -31,7 +31,7 @@ namespace Quotient { * upgrade appropriately. Additionally, clients should avoid using unstable * features in their stable releases. */ -class GetVersionsJob : public BaseJob { +class QUOTIENT_API GetVersionsJob : public BaseJob { public: /// Gets the versions of the specification supported by the server. explicit GetVersionsJob(); diff --git a/lib/csapi/voip.h b/lib/csapi/voip.h index 087ebbbd..38904f60 100644 --- a/lib/csapi/voip.h +++ b/lib/csapi/voip.h @@ -13,7 +13,7 @@ namespace Quotient { * This API provides credentials for the client to use when initiating * calls. */ -class GetTurnServerJob : public BaseJob { +class QUOTIENT_API GetTurnServerJob : public BaseJob { public: /// Obtain TURN server credentials. explicit GetTurnServerJob(); diff --git a/lib/csapi/wellknown.h b/lib/csapi/wellknown.h index c707d232..8615191c 100644 --- a/lib/csapi/wellknown.h +++ b/lib/csapi/wellknown.h @@ -21,7 +21,7 @@ namespace Quotient { * Note that this endpoint is not necessarily handled by the homeserver, * but by another webserver, to be used for discovering the homeserver URL. */ -class GetWellknownJob : public BaseJob { +class QUOTIENT_API GetWellknownJob : public BaseJob { public: /// Gets Matrix server discovery information about the domain. explicit GetWellknownJob(); diff --git a/lib/csapi/whoami.h b/lib/csapi/whoami.h index 319f82c5..fba099f6 100644 --- a/lib/csapi/whoami.h +++ b/lib/csapi/whoami.h @@ -19,7 +19,7 @@ namespace Quotient { * is registered by the appservice, and return it in the response * body. */ -class GetTokenOwnerJob : public BaseJob { +class QUOTIENT_API GetTokenOwnerJob : public BaseJob { public: /// Gets information about the owner of an access token. explicit GetTokenOwnerJob(); -- cgit v1.2.3 From 7eda212753057c07f429dfdfb0cf3a18312de054 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 28 Dec 2021 21:46:57 +0100 Subject: Refactor EventFactory and move it out of _impl:: Strictly speaking, EventFactory can be further instantiated if any client application figures they need a whole new base class for events and respectively a separate EventFactory specialisation for it. Where this whole commit started though was a linkage error because I did not plan to expose Quotient-specific logging categories for linkage (effectively, usage) from the client code - meanwhile the inline code of EventFactory uses qDebug(EVENTS), meaning I had to either add QUOTIENT_API to EVENTS or hide those invocations. This in turn led to trimming the EventFactory constructor back to trivial implementation and dropping the guard variable that was supposed to trace duplicate EventFactory objects for the same BaseEventT - with the reasoning that such situation is not really dangerous (unlike EventTypeRegistry double-initialisation fiasco, see #413), and at the same time it can be easily detected in the logs by duplicated factory method registration messages. And while I was at it, I replaced the meaningless bool in the return type of EventFactory<>::addMethod with the slightly more (but still barely) useful reference to the inserted factory method. One can (in theory) use it now if they need to turn some event JSON into an object of some specific event type or nullptr if the event type in the JSON payload doesn't match - but at the same rate (for now at least) one can call makeIfMatches() directly. With this commit, both Quotest and Quaternion build and link using either Clang or GCC even under -fvisibility=hidden. However, running quotest now reproduces #413, which is a matter of event typeId infrastructure refactoring, coming in further commits. --- lib/events/event.cpp | 8 +++ lib/events/event.h | 159 ++++++++++++++++++++++++------------------------ lib/events/roomevent.h | 2 +- lib/events/stateevent.h | 2 +- 4 files changed, 88 insertions(+), 83 deletions(-) diff --git a/lib/events/event.cpp b/lib/events/event.cpp index 96be717c..715e7da2 100644 --- a/lib/events/event.cpp +++ b/lib/events/event.cpp @@ -27,6 +27,14 @@ QString EventTypeRegistry::getMatrixType(event_type_t typeId) : QString(); } +void _impl::EventFactoryBase::logAddingMethod(event_mtype_t matrixType, + size_t newSize) +{ + qDebug(EVENTS) << "Adding factory method for" << matrixType << "events;" + << newSize << "methods will be in the" << name + << "chain"; +} + Event::Event(Type type, const QJsonObject& json) : _type(type), _json(json) { if (!json.contains(ContentKeyL) diff --git a/lib/events/event.h b/lib/events/event.h index 8a0076d0..0aef49f7 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -120,80 +120,93 @@ inline event_ptr_tt makeEvent(ArgTs&&... args) } namespace _impl { - template - event_ptr_tt makeIfMatches(const QJsonObject& json, - const QString& matrixType) + class QUOTIENT_API EventFactoryBase { + public: + EventFactoryBase(const EventFactoryBase&) = delete; + + protected: // This class is only to inherit from + explicit EventFactoryBase(const char* name) + : name(name) + {} + void logAddingMethod(event_mtype_t mtypeId, size_t newSize); + + private: + const char* const name; + }; +} // namespace _impl + +//! \brief A family of event factories to create events from CS API responses +//! +//! Each of these factories, as instantiated by event base types (Event, +//! RoomEvent etc.) is capable of producing an event object derived from +//! \p BaseEventT, using the JSON payload and the event type passed to its +//! make() method. Don't use these directly to make events; use loadEvent() +//! overloads as the frontend for these. Never instantiate new factories +//! outside of base event classes. +//! \sa loadEvent, setupFactory, Event::factory, RoomEvent::factory, +//! StateEventBase::factory +template +class EventFactory : public _impl::EventFactoryBase { +private: + std::vector (*)(const QJsonObject&, const QString&)> + methods {}; + + template + static event_ptr_tt makeIfMatches(const QJsonObject& json, + const QString& matrixType) { return QLatin1String(EventT::matrixTypeId()) == matrixType ? makeEvent(json) : nullptr; } - //! \brief A family of event factories to create events from CS API responses +public: + explicit EventFactory(const char* fName) + : EventFactoryBase { fName } + {} + + //! \brief Add a method to create events of a given type //! - //! Each of these factories, as instantiated by event base types (Event, - //! RoomEvent etc.) is capable of producing an event object derived from - //! \p BaseEventT, using the JSON payload and the event type passed to its - //! make() method. Don't use these directly to make events; use loadEvent() - //! overloads as the frontend for these. Never instantiate new factories - //! outside of base event classes. - //! \sa loadEvent, setupFactory, Event::factory, RoomEvent::factory, - //! StateEventBase::factory - template - class EventFactory - : private std::vector (*)(const QJsonObject&, - const QString&)> { - // Actual makeIfMatches specialisations will differ in the first - // template parameter but that doesn't affect the function type - public: - explicit EventFactory(const char* name = "") - : name(name) - { - static auto yetToBeConstructed = true; - Q_ASSERT(yetToBeConstructed); - if (!yetToBeConstructed) // For Release builds that pass Q_ASSERT - qCritical(EVENTS) - << "Another EventFactory for the same base type is being " - "created - event creation logic will be splintered"; - yetToBeConstructed = false; - } - EventFactory(const EventFactory&) = delete; - - //! \brief Add a method to create events of a given type - //! - //! Adds a standard factory method (makeIfMatches) for \p EventT so that - //! event objects of this type can be created dynamically by loadEvent. - //! The caller is responsible for ensuring this method is called only - //! once per type. - //! \sa makeIfMatches, loadEvent, Quotient::loadEvent - template - bool addMethod() - { - this->emplace_back(&makeIfMatches); - qDebug(EVENTS) << "Added factory method for" - << EventT::matrixTypeId() << "events;" << this->size() - << "methods in the" << name << "chain by now"; - return true; - } - - auto loadEvent(const QJsonObject& json, const QString& matrixType) - { - for (const auto& f : *this) - if (auto e = f(json, matrixType)) - return e; - return makeEvent(unknownEventTypeId(), json); - } + //! Adds a standard factory method (makeIfMatches) for \p EventT so that + //! event objects of this type can be created dynamically by loadEvent. + //! The caller is responsible for ensuring this method is called only + //! once per type. + //! \sa loadEvent, Quotient::loadEvent + template + const auto& addMethod() + { + logAddingMethod(EventT::matrixTypeId(), methods.size() + 1); + return methods.emplace_back(&makeIfMatches); + } - const char* const name; - }; -} // namespace _impl + auto loadEvent(const QJsonObject& json, const QString& matrixType) + { + for (const auto& f : methods) + if (auto e = f(json, matrixType)) + return e; + return makeEvent(unknownEventTypeId(), json); + } +}; + +//! \brief Point of customisation to dynamically load events +//! +//! The default specialisation of this calls BaseEventT::factory.loadEvent() +//! and if that fails (i.e. returns nullptr) creates an unknown event of +//! BaseEventT. Other specialisations may reuse other factories, add validations +//! common to BaseEventT events, and so on. +template +event_ptr_tt doLoadEvent(const QJsonObject& json, + const QString& matrixType) +{ + return BaseEventT::factory.loadEvent(json, matrixType); +} // === Event === class QUOTIENT_API Event { public: using Type = event_type_t; - static inline _impl::EventFactory factory { "Event" }; + static inline EventFactory factory { "Event" }; explicit Event(Type type, const QJsonObject& json); explicit Event(Type type, event_mtype_t matrixType, @@ -273,37 +286,21 @@ using Events = EventsArray; // This macro should be used in a public section of an event class to // provide matrixTypeId() and typeId(). #define DEFINE_EVENT_TYPEID(_Id, _Type) \ - static QUOTIENT_EXPORT constexpr event_mtype_t matrixTypeId() \ + static QUOTIENT_API constexpr event_mtype_t matrixTypeId() \ { \ return _Id; \ } \ - static QUOTIENT_EXPORT auto typeId() { return Quotient::typeId<_Type>(); } \ + static QUOTIENT_API auto typeId() { return Quotient::typeId<_Type>(); } \ // End of macro // This macro should be put after an event class definition (in .h or .cpp) // to enable its deserialisation from a /sync and other // polymorphic event arrays -#define REGISTER_EVENT_TYPE(_Type) \ - [[maybe_unused]] QUOTIENT_API inline const auto _factoryAdded##_Type = \ - _Type::factory.addMethod<_Type>(); \ +#define REGISTER_EVENT_TYPE(Type_) \ + [[maybe_unused]] QUOTIENT_API inline const auto& factoryMethodFor##Type_ = \ + Type_::factory.addMethod(); \ // End of macro -// === Event loading === -// (see also event_loader.h) - -//! \brief Point of customisation to dynamically load events -//! -//! The default specialisation of this calls BaseEventT::factory and if that -//! fails (i.e. returns nullptr) creates an unknown event of BaseEventT. -//! Other specialisations may reuse other factories, add validations common to -//! BaseEventT, and so on -template -event_ptr_tt doLoadEvent(const QJsonObject& json, - const QString& matrixType) -{ - return BaseEventT::factory.loadEvent(json, matrixType); -} - // === is<>(), eventCast<>() and switchOnType<>() === template diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 3fbb247e..dcee1170 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -13,7 +13,7 @@ class RedactionEvent; /** This class corresponds to m.room.* events */ class QUOTIENT_API RoomEvent : public Event { public: - static inline _impl::EventFactory factory { "RoomEvent" }; + static inline EventFactory factory { "RoomEvent" }; // RedactionEvent is an incomplete type here so we cannot inline // constructors and destructors and we cannot use 'using'. diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index 6095d628..88da68f8 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -19,7 +19,7 @@ inline QJsonObject basicStateEventJson(const QString& matrixTypeId, class QUOTIENT_API StateEventBase : public RoomEvent { public: - static inline _impl::EventFactory factory { "StateEvent" }; + static inline EventFactory factory { "StateEvent" }; StateEventBase(Type type, const QJsonObject& json); StateEventBase(Type type, event_mtype_t matrixType, -- cgit v1.2.3 From 806bfd6da07127c33d1014ef2335a8b9602afe6c Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 29 Dec 2021 18:17:38 +0100 Subject: CMakeLists.txt: apply -fvisibility=hidden Also, -fvisibility-inlines-hidden is applied in a CMake-native way now. As can be expected, BUILDING_SHARED_QUOTIENT is set when a dynamic library is built while QUOTIENT_STATIC is set whenever static libQuotient is around (both for building it and for building with it). --- CMakeLists.txt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0fc47b42..45aa3726 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,7 +40,7 @@ if (MSVC) /wd4710 /wd4774 /wd4820 /wd4946 /wd5026 /wd5027) else() foreach (FLAG Wall Wpedantic Wextra Werror=return-type Wno-unused-parameter - Wno-gnu-zero-variadic-macro-arguments fvisibility-inlines-hidden) + Wno-gnu-zero-variadic-macro-arguments) CHECK_CXX_COMPILER_FLAG("-${FLAG}" COMPILER_${FLAG}_SUPPORTED) if ( COMPILER_${FLAG}_SUPPORTED AND NOT CMAKE_CXX_FLAGS MATCHES "(^| )-?${FLAG}($| )") @@ -254,6 +254,12 @@ file(GLOB_RECURSE api_ALL_SRCS CONFIGURE_DEPENDS ${FULL_CSAPI_DIR}/*.* lib/${ASAPI_DEF_DIR}/*.* lib/${ISAPI_DEF_DIR}/*.*) add_library(${PROJECT_NAME} ${lib_SRCS} ${api_ALL_SRCS}) +# Set BUILDING_SHARED_QUOTIENT if building as a shared library +target_compile_definitions(${PROJECT_NAME} PRIVATE + $<$,SHARED_LIBRARY>:BUILDING_SHARED_QUOTIENT>) +# Set QUOTIENT_STATIC in a static library setting +target_compile_definitions(${PROJECT_NAME} PUBLIC + $<$,STATIC_LIBRARY>:QUOTIENT_STATIC>) target_compile_definitions(${PROJECT_NAME} PRIVATE QT_NO_JAVA_STYLE_ITERATORS QT_NO_URL_CAST_FROM_STRING QT_NO_CAST_TO_ASCII) target_compile_definitions(${PROJECT_NAME} PUBLIC ${PROJECT_NAME}_VERSION_MAJOR=${PROJECT_VERSION_MAJOR} @@ -265,6 +271,8 @@ endif() set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 20 CXX_EXTENSIONS OFF + VISIBILITY_INLINES_HIDDEN ON + CXX_VISIBILITY_PRESET hidden VERSION "${PROJECT_VERSION}" SOVERSION ${API_VERSION} INTERFACE_${PROJECT_NAME}_MAJOR_VERSION ${API_VERSION} -- cgit v1.2.3 From ac7ee73cb4fa1ac05f7bcae1800cb79b36c7647f Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 28 Dec 2021 20:20:41 +0100 Subject: CI: Put the shared object configuration to the test For now on Linux with GCC only, with a plan to add Windows eventually. --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a1b6f0c0..e4bf8e29 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -105,8 +105,10 @@ jobs: fi echo "QUOTEST_ORIGIN=$VERSION @ ${{ runner.os }}/${{ matrix.compiler }}" >>$GITHUB_ENV + # Build libQuotient as a shared library for the sonar-enabled run + # to have this configuration also covered CMAKE_ARGS="-G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo \ - -DBUILD_SHARED_LIBS=false \ + -DBUILD_SHARED_LIBS=${{ matrix.sonar }} \ -DCMAKE_INSTALL_PREFIX=~/.local \ -DCMAKE_PREFIX_PATH=~/.local \ -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=ON" -- cgit v1.2.3 From 27bb7ba696ae803c6a6903f85fe14074b23b7bcc Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 28 Dec 2021 21:34:03 +0100 Subject: Use QLatin1String for event typeId's Before all, this fixes the problem with double-initialising of type ids; it could have been fixed with a smaller change but EventTypeRegistry is fairly superfluous now when inline variables are a thing and it's possible to have an extensible registry system using literally pointers to the memory that are guaranteed to be unique. That being said, event_type_t is still QLatin1String and not a bare const char* (or void*), mostly to stay on the safe side when it comes to type identities: unlike const char*, QLatin1String's are deep-compared, meaning that matching for switchOnType (former visit) occurs a bit slower now. This may change in the future; but this is the first step in getting rid of EventTypeRegistry. This change means that initializeTypeId is no more needed; also, two static member functions, typeId() and matrixTypeId(), are being replaced with a single inline static member variable, TypeId. This commit doesn't apply that transition across the event types, meaning that you'll get a pile of warnings when compiling the library. These warnings will be tackled in further commits within this branch. --- lib/events/event.cpp | 22 +++-------------- lib/events/event.h | 68 ++++++++++++++++------------------------------------ 2 files changed, 23 insertions(+), 67 deletions(-) diff --git a/lib/events/event.cpp b/lib/events/event.cpp index 715e7da2..4c304a3c 100644 --- a/lib/events/event.cpp +++ b/lib/events/event.cpp @@ -9,28 +9,12 @@ using namespace Quotient; -event_type_t EventTypeRegistry::initializeTypeId(event_mtype_t matrixTypeId) -{ - const auto id = get().eventTypes.size(); - get().eventTypes.push_back(matrixTypeId); - if (strncmp(matrixTypeId, "", 1) == 0) - qDebug(EVENTS) << "Initialized unknown event type with id" << id; - else - qDebug(EVENTS) << "Initialized event type" << matrixTypeId << "with id" - << id; - return id; -} - -QString EventTypeRegistry::getMatrixType(event_type_t typeId) -{ - return typeId < get().eventTypes.size() ? get().eventTypes[typeId] - : QString(); -} +QString EventTypeRegistry::getMatrixType(event_type_t typeId) { return typeId; } -void _impl::EventFactoryBase::logAddingMethod(event_mtype_t matrixType, +void _impl::EventFactoryBase::logAddingMethod(event_type_t TypeId, size_t newSize) { - qDebug(EVENTS) << "Adding factory method for" << matrixType << "events;" + qDebug(EVENTS) << "Adding factory method for" << TypeId << "events;" << newSize << "methods will be in the" << name << "chain"; } diff --git a/lib/events/event.h b/lib/events/event.h index 0aef49f7..47f07c1d 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -55,60 +55,32 @@ inline QJsonObject basicEventJson(const QString& matrixType, return { { TypeKey, matrixType }, { ContentKey, content } }; } -// === Event types and event types registry === +// === Event types === -using event_type_t = size_t; +using event_type_t = QLatin1String; using event_mtype_t = const char*; class QUOTIENT_API EventTypeRegistry { public: ~EventTypeRegistry() = default; - static event_type_t initializeTypeId(event_mtype_t matrixTypeId); - - template - static event_type_t initializeTypeId() - { - return initializeTypeId(EventT::matrixTypeId()); - } - + [[deprecated("event_type_t is a string now, use it directly instead")]] static QString getMatrixType(event_type_t typeId); private: EventTypeRegistry() = default; Q_DISABLE_COPY_MOVE(EventTypeRegistry) - - static EventTypeRegistry& get() - { - static EventTypeRegistry etr; - return etr; - } - - std::vector eventTypes; -}; - -template <> -inline event_type_t EventTypeRegistry::initializeTypeId() -{ - return initializeTypeId(""); -} - -template -struct EventTypeTraits { - static event_type_t id() - { - static const auto id = EventTypeRegistry::initializeTypeId(); - return id; - } }; template inline event_type_t typeId() { - return EventTypeTraits>::id(); + return std::decay_t::TypeId; } -inline event_type_t unknownEventTypeId() { return typeId(); } +constexpr inline event_type_t UnknownEventTypeId = "?"_ls; +[[deprecated("Use UnknownEventTypeId")]] +constexpr inline event_type_t unknownEventTypeId() { return UnknownEventTypeId; } // === Event creation facilities === @@ -128,7 +100,7 @@ namespace _impl { explicit EventFactoryBase(const char* name) : name(name) {} - void logAddingMethod(event_mtype_t mtypeId, size_t newSize); + void logAddingMethod(event_type_t TypeId, size_t newSize); private: const char* const name; @@ -155,9 +127,9 @@ private: static event_ptr_tt makeIfMatches(const QJsonObject& json, const QString& matrixType) { - return QLatin1String(EventT::matrixTypeId()) == matrixType - ? makeEvent(json) - : nullptr; + // If your matrix event type is not all ASCII, it's your problem + // (see https://github.com/matrix-org/matrix-doc/pull/2758) + return EventT::TypeId == matrixType ? makeEvent(json) : nullptr; } public: @@ -175,7 +147,7 @@ public: template const auto& addMethod() { - logAddingMethod(EventT::matrixTypeId(), methods.size() + 1); + logAddingMethod(EventT::TypeId, methods.size() + 1); return methods.emplace_back(&makeIfMatches); } @@ -184,7 +156,7 @@ public: for (const auto& f : methods) if (auto e = f(json, matrixType)) return e; - return makeEvent(unknownEventTypeId(), json); + return makeEvent(UnknownEventTypeId, json); } }; @@ -285,12 +257,12 @@ using Events = EventsArray; // This macro should be used in a public section of an event class to // provide matrixTypeId() and typeId(). -#define DEFINE_EVENT_TYPEID(_Id, _Type) \ - static QUOTIENT_API constexpr event_mtype_t matrixTypeId() \ - { \ - return _Id; \ - } \ - static QUOTIENT_API auto typeId() { return Quotient::typeId<_Type>(); } \ +#define DEFINE_EVENT_TYPEID(Id_, Type_) \ + static inline constexpr event_type_t TypeId = Id_##_ls; \ + [[deprecated("Use _Type::TypeId directly instead")]] \ + static constexpr event_mtype_t matrixTypeId() { return Id_; } \ + [[deprecated("Use _Type::TypeId directly instead")]] \ + static event_type_t typeId() { return TypeId; } \ // End of macro // This macro should be put after an event class definition (in .h or .cpp) @@ -311,7 +283,7 @@ inline bool is(const Event& e) inline bool isUnknown(const Event& e) { - return e.type() == unknownEventTypeId(); + return e.type() == UnknownEventTypeId; } template -- cgit v1.2.3 From 783d83012551df128d044382f6dd2047c5269700 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 1 Jan 2022 12:48:28 +0100 Subject: OtherChange is Change::Other now `Room::Change` has been changed to be an enum class recently; and it's values are no more suffixed with `Change`. --- lib/room.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/room.cpp b/lib/room.cpp index 6854879c..edf5dcd9 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -2925,7 +2925,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) } , [this] (const RoomPinnedEvent&) { emit pinnedEventsChanged(); - return OtherChange; + return Change::Other; } , [] (const RoomTopicEvent&) { return Change::Topic; -- cgit v1.2.3 From cae9d908e8b319e8a0c33f7089ecb7f2e9317de8 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 1 Jan 2022 13:40:09 +0100 Subject: CI: up to Qt 5.12.12 --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a1b6f0c0..5c2c820e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: matrix: os: [ubuntu-20.04, macos-10.15] compiler: [ GCC, Clang ] - qt-version: [ '5.12.10' ] + qt-version: [ '5.12.12' ] # Not using binary values here, to make the job captions more readable e2ee: [ '' ] update-api: [ '', 'update-api' ] @@ -34,17 +34,17 @@ jobs: include: - os: ubuntu-latest compiler: GCC - qt-version: '5.12.10' + qt-version: '5.12.12' sonar: 'sonar' - os: windows-2019 compiler: MSVC platform: x64 - qt-version: '5.12.10' + qt-version: '5.12.12' qt-arch: win64_msvc2017_64 - os: windows-2019 compiler: MSVC platform: x64 - qt-version: '5.12.10' + qt-version: '5.12.12' qt-arch: win64_msvc2017_64 update-api: update-api -- cgit v1.2.3 From e4511d02a20e53e1aa2e5d4886bd68dd73f885b3 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 1 Jan 2022 19:21:59 +0100 Subject: Get LGTM working again Unfortunately LGTM still sits on Ubuntu 19.10 (not even LTS) which really limits the choice of compilers: the newest GCC is of version 9 and the newest Clang seems to be version 9 as well - both are quite old and giving when it comes to modern features support. The latest error is GCC 9 tripping over the assignment of the function specialisation to std::function. Clang 9 doesn't and otherwise seems fine. --- .lgtm.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.lgtm.yml b/.lgtm.yml index 308675a8..a01e1de9 100644 --- a/.lgtm.yml +++ b/.lgtm.yml @@ -15,4 +15,4 @@ extraction: # - cmake --build build # - popd configure: - command: "cmake . -GNinja" # -DOlm_DIR=olm/build" + command: "CXX=clang++-9 cmake . -GNinja" # -DOlm_DIR=olm/build" -- cgit v1.2.3 From 545f9084bacead9f02ba6df609822be4672b7952 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 1 Jan 2022 20:02:05 +0100 Subject: CI: add events.debug to QT_LOGGING_RULES --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c2c820e..e56ea26e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -198,7 +198,7 @@ jobs: env: TEST_USER: ${{ secrets.TEST_USER }} TEST_PWD: ${{ secrets.TEST_PWD }} - QT_LOGGING_RULES: 'quotient.main.debug=true;quotient.jobs.debug=true' + QT_LOGGING_RULES: 'quotient.main.debug=true;quotient.jobs.debug=true;quotient.events.debug=true' QT_MESSAGE_PATTERN: '%{time h:mm:ss.zzz}|%{category}|%{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}|%{message}' run: | ctest --test-dir $BUILD_PATH --output-on-failure -- cgit v1.2.3 From bbaaab6590e5435fd9c967ca4af5ef06d9182698 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 1 Jan 2022 20:41:04 +0100 Subject: BUILD_SHARED_LIBS in most jobs; fix not finding libQuotient.so.* --- .github/workflows/ci.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e4bf8e29..d4af9b30 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -105,10 +105,10 @@ jobs: fi echo "QUOTEST_ORIGIN=$VERSION @ ${{ runner.os }}/${{ matrix.compiler }}" >>$GITHUB_ENV - # Build libQuotient as a shared library for the sonar-enabled run - # to have this configuration also covered + # Build libQuotient as a shared library across platforms but also + # check the static configuration somewhere CMAKE_ARGS="-G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo \ - -DBUILD_SHARED_LIBS=${{ matrix.sonar }} \ + -DBUILD_SHARED_LIBS=${{ !matrix.sonar }} \ -DCMAKE_INSTALL_PREFIX=~/.local \ -DCMAKE_PREFIX_PATH=~/.local \ -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=ON" @@ -122,6 +122,7 @@ jobs: if [[ '${{ runner.os }}' != 'Windows' ]]; then BIN_DIR=/bin + echo "LIB_PATH=$HOME/.local/lib" >>$GITHUB_ENV fi echo "BIN_DIR=$BIN_DIR" >>$GITHUB_ENV echo "~/.local$BIN_DIR" >>$GITHUB_PATH @@ -205,6 +206,7 @@ jobs: run: | ctest --test-dir $BUILD_PATH --output-on-failure [[ -z "$TEST_USER" ]] || \ + LD_LIBRARY_PATH=$LIB_PATH \ $VALGRIND quotest "$TEST_USER" "$TEST_PWD" quotest-gha '#quotest:matrix.org' "$QUOTEST_ORIGIN" timeout-minutes: 4 # quotest is supposed to finish within 3 minutes, actually -- cgit v1.2.3 From 39b50a13a0379ea32be114c85f3c697d75d4e03b Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 29 Dec 2021 21:22:26 +0100 Subject: Quotest: build with hidden visibility too --- quotest/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/quotest/CMakeLists.txt b/quotest/CMakeLists.txt index cb41141d..ec305620 100644 --- a/quotest/CMakeLists.txt +++ b/quotest/CMakeLists.txt @@ -8,6 +8,11 @@ find_package(${Qt} COMPONENTS Concurrent) add_executable(quotest ${quotest_SRCS}) target_link_libraries(quotest PRIVATE ${Qt}::Core ${Qt}::Test ${Qt}::Concurrent ${PROJECT_NAME}) +set_target_properties(quotest PROPERTIES + VISIBILITY_INLINES_HIDDEN ON + CXX_VISIBILITY_PRESET hidden +) + if (MSVC) target_compile_options(quotest PUBLIC /EHsc /W4 /wd4100 /wd4127 /wd4242 /wd4244 /wd4245 /wd4267 /wd4365 /wd4456 /wd4459 -- cgit v1.2.3 From 5b8ea1a0d419fda1729aaa81e34ad20e0dacef44 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 30 Dec 2021 10:40:07 +0100 Subject: Try to fix building with MSVC with Qt pre-5.14 MSVC is quite picky to redeclaration with __declspec(dllexport), judging it as changing the class of storage. This commit tries to reorder declarations so that MSVC is made aware of dllexport attribute on the first encounter rather than the second one. --- lib/quotient_common.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/quotient_common.h b/lib/quotient_common.h index a5926e8c..3d38ce1f 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -29,13 +29,13 @@ Deprecated Q_DECL_ENUMERATOR_DEPRECATED_X("Use " #Recommended) = Recommended #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) -// The first line is a usual way to indicate a namespace to moc; -// the second line redeclares the namespace static metaobject with -// QUOTIENT_API so that dynamically linked clients could serialise -// flag/enum values from the namespace. +// The first line forward-declares the namespace static metaobject with +// QUOTIENT_API so that dynamically linked clients could serialise flag/enum +// values from the namespace; Qt before 5.14 doesn't help with that. The second +// line is needed for moc to do its job on the namespace. #define QUO_NAMESPACE \ -Q_NAMESPACE \ -extern QUOTIENT_API const QMetaObject staticMetaObject; + extern QUOTIENT_API const QMetaObject staticMetaObject; \ + Q_NAMESPACE #else // Since Qt 5.14.0, it's all packed in a single macro #define QUO_NAMESPACE Q_NAMESPACE_EXPORT(QUOTIENT_API) -- cgit v1.2.3 From a5a261b2c0dc60f99c8caa4729683d2780677f88 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 30 Dec 2021 12:46:50 +0100 Subject: Define destructors out-of-line when unique/scoped ptr are involved Once visibility kicks in, MSVC changes its ways and tries to instantiate Private classes wrapped in smart pointers upon their occurence in the header file - which leads to build breakage because of a missing destructor. Usually making the outer class destructor out-of-line helps to fix this (see RoomEvent, for one example). --- lib/jobs/downloadfilejob.cpp | 2 ++ lib/jobs/downloadfilejob.h | 1 + lib/mxcreply.cpp | 2 ++ lib/mxcreply.h | 2 ++ 4 files changed, 7 insertions(+) diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 0b0531ad..6bf221cb 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -37,6 +37,8 @@ DownloadFileJob::DownloadFileJob(const QString& serverName, setObjectName(QStringLiteral("DownloadFileJob")); } +DownloadFileJob::~DownloadFileJob() = default; + QString DownloadFileJob::targetFileName() const { return (d->targetFile ? d->targetFile : d->tempFile)->fileName(); diff --git a/lib/jobs/downloadfilejob.h b/lib/jobs/downloadfilejob.h index d9f3b686..9e807fe7 100644 --- a/lib/jobs/downloadfilejob.h +++ b/lib/jobs/downloadfilejob.h @@ -13,6 +13,7 @@ public: DownloadFileJob(const QString& serverName, const QString& mediaId, const QString& localFilename = {}); + ~DownloadFileJob() override; QString targetFileName() const; diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index 0b6643fc..fb16c79f 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -38,6 +38,8 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) }); } +MxcReply::~MxcReply() = default; + #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) #define ERROR_SIGNAL errorOccurred #else diff --git a/lib/mxcreply.h b/lib/mxcreply.h index 23049b7d..1d31d608 100644 --- a/lib/mxcreply.h +++ b/lib/mxcreply.h @@ -13,10 +13,12 @@ class Room; class QUOTIENT_API MxcReply : public QNetworkReply { + Q_OBJECT public: explicit MxcReply(); explicit MxcReply(QNetworkReply *reply); MxcReply(QNetworkReply* reply, Room* room, const QString& eventId); + ~MxcReply() override; public Q_SLOTS: void abort() override; -- cgit v1.2.3 From 8f03628ee0e4d1d1cb4e2f237e8fa695bc2cde42 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 30 Dec 2021 15:24:58 +0100 Subject: Drop inline next to constexpr Thanks to Sonar for reminding that constexpr implies inline. --- lib/events/event.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/events/event.h b/lib/events/event.h index 47f07c1d..692e88e7 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -78,9 +78,9 @@ inline event_type_t typeId() return std::decay_t::TypeId; } -constexpr inline event_type_t UnknownEventTypeId = "?"_ls; +constexpr event_type_t UnknownEventTypeId = "?"_ls; [[deprecated("Use UnknownEventTypeId")]] -constexpr inline event_type_t unknownEventTypeId() { return UnknownEventTypeId; } +constexpr event_type_t unknownEventTypeId() { return UnknownEventTypeId; } // === Event creation facilities === @@ -258,7 +258,7 @@ using Events = EventsArray; // This macro should be used in a public section of an event class to // provide matrixTypeId() and typeId(). #define DEFINE_EVENT_TYPEID(Id_, Type_) \ - static inline constexpr event_type_t TypeId = Id_##_ls; \ + static constexpr event_type_t TypeId = Id_##_ls; \ [[deprecated("Use _Type::TypeId directly instead")]] \ static constexpr event_mtype_t matrixTypeId() { return Id_; } \ [[deprecated("Use _Type::TypeId directly instead")]] \ -- cgit v1.2.3 From 22ac47b275c2bcad5b5ff3c0cc3e10f3caaeb65b Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 30 Dec 2021 15:46:11 +0100 Subject: Don't use QUOTIENT_API inside REGISTER_EVENT_TYPE On Windows QUOTIENT_API expands to different things depending on whether the library is built or used. This results in confusing statements (and MSVC erroring out on them, in some cases - see below - quite legitimately) not only when the application includes Quotient headers but also when the application defines custom events and uses REGISTER_EVENT_TYPE to make them creatable from /sync responses. To avoid repeated registration when dynamic linking is involved, EventFactory<>::addMethod() now bluntly looks up the method for this type in the vector of already registered methods. It would surely be quicker to use a static variable instead; but since the refreshed API for addMethod returns a reference to the factory method it's necessary to do this lookup anyway. Once the primary goal of this branch is achieved across platforms I might experiment with lighter ways to register factory methods; for now here's a minimal change to make the code build on Windows. --- lib/events/event.h | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/events/event.h b/lib/events/event.h index 692e88e7..4024c6f8 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -73,7 +73,7 @@ private: }; template -inline event_type_t typeId() +constexpr event_type_t typeId() { return std::decay_t::TypeId; } @@ -147,8 +147,12 @@ public: template const auto& addMethod() { + const auto m = &makeIfMatches; + const auto it = std::find(methods.cbegin(), methods.cend(), m); + if (it != methods.cend()) + return *it; logAddingMethod(EventT::TypeId, methods.size() + 1); - return methods.emplace_back(&makeIfMatches); + return methods.emplace_back(m); } auto loadEvent(const QJsonObject& json, const QString& matrixType) @@ -268,9 +272,9 @@ using Events = EventsArray; // This macro should be put after an event class definition (in .h or .cpp) // to enable its deserialisation from a /sync and other // polymorphic event arrays -#define REGISTER_EVENT_TYPE(Type_) \ - [[maybe_unused]] QUOTIENT_API inline const auto& factoryMethodFor##Type_ = \ - Type_::factory.addMethod(); \ +#define REGISTER_EVENT_TYPE(Type_) \ + [[maybe_unused]] inline const auto& factoryMethodFor##Type_ = \ + Type_::factory.addMethod(); \ // End of macro // === is<>(), eventCast<>() and switchOnType<>() === -- cgit v1.2.3 From 874ea3fae21d6b5cab12c8e524e8b25442e4cdd5 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 30 Dec 2021 17:55:23 +0100 Subject: Fix more Sonar warnings --- lib/events/event.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/events/event.h b/lib/events/event.h index 4024c6f8..f12e525e 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -120,8 +120,9 @@ namespace _impl { template class EventFactory : public _impl::EventFactoryBase { private: - std::vector (*)(const QJsonObject&, const QString&)> - methods {}; + using method_t = event_ptr_tt (*)(const QJsonObject&, + const QString&); + std::vector methods {}; template static event_ptr_tt makeIfMatches(const QJsonObject& json, -- cgit v1.2.3 From ca42b3659e1916d384d092b8c31c49e3ffd6441b Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 30 Dec 2021 17:56:52 +0100 Subject: CMakeLists: Drop unneeded parts from install(TARGETS) Those DESTINATION specifications match precisely what CMake does by default (on Linux at least); what's worse is that they prevent CMake to install the DLL file on Windows. --- CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 45aa3726..c889cf13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -316,8 +316,6 @@ endif() # Configure installation install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}Targets - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} INCLUDES DESTINATION ${${PROJECT_NAME}_INSTALL_INCLUDEDIR} ) install(DIRECTORY lib/ DESTINATION ${${PROJECT_NAME}_INSTALL_INCLUDEDIR} -- cgit v1.2.3 From 2dee2bf4f0b2bd6615866644b2df9460da6babbb Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 1 Jan 2022 20:46:12 +0100 Subject: Only test dynamic linking on Linux On Windows and macOS, the assumed practice is to package things up in a monolithic distribution package. On Linux, fine-grained library packages are installed. After adding CMAKE_INSTALL_RPATH_USE_LINK_PATH dynamic linkage is testable on macOS but that's not the "proper" way to install things on the platform, anyway. It also probably works on Windows but I couldn't get a workable setup for quotest after a few attempts. Perhaps it's a matter of running windeployqt instead of trying to figure out and add to PATH all the locations needed; or maybe not; I don't have the motivation to explore this further. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d4af9b30..ef6ea3d2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -108,7 +108,7 @@ jobs: # Build libQuotient as a shared library across platforms but also # check the static configuration somewhere CMAKE_ARGS="-G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo \ - -DBUILD_SHARED_LIBS=${{ !matrix.sonar }} \ + -DBUILD_SHARED_LIBS=${{ !matrix.sonar && runner.os == 'Linux' }} \ -DCMAKE_INSTALL_PREFIX=~/.local \ -DCMAKE_PREFIX_PATH=~/.local \ -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=ON" -- cgit v1.2.3 From ca6a104941b71e7b6a8bdcb6ebcdfff5ec8e8aca Mon Sep 17 00:00:00 2001 From: Vladyslav Hnatiuk Date: Sat, 1 Jan 2022 22:14:01 +0100 Subject: Fix enum in room Notification struct --- lib/room.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/room.h b/lib/room.h index cbe3d1ad..a5513727 100644 --- a/lib/room.h +++ b/lib/room.h @@ -101,7 +101,7 @@ struct EventStats; struct Notification { enum Type { None = 0, Basic, Highlight }; - Q_ENUM(Notification) + Q_ENUM(Type) Type type = None; -- cgit v1.2.3 From 7d37d296f942ac993d041b4576ed52265170c4a8 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 2 Jan 2022 06:03:26 +0100 Subject: Add ImplPtr and makeImpl The original (more complex and comprehensive) solution belongs to https://oliora.github.io/2015/12/29/pimpl-and-rule-of-zero.html - this commit only provides a small wrapper for non-copyable Private class implementations common throughout libQuotient. Unlike the original, default initialisation is made explicit - you have to pass ZeroImpl() instead (and I firmly believe it's a good thing: normally pointers to Private should not remain nullptr). The reason ZeroImpl<> is not a template variable is quite simple: unique_ptr is non-copyable and so cannot be initialised from; while a template function will initialise the value in-place thanks to copy elision. --- lib/avatar.cpp | 12 ++++-------- lib/avatar.h | 8 ++------ lib/connection.cpp | 3 ++- lib/connection.h | 2 +- lib/connectiondata.cpp | 2 +- lib/connectiondata.h | 5 +++-- lib/jobs/basejob.cpp | 4 ++-- lib/jobs/basejob.h | 2 +- lib/jobs/downloadfilejob.cpp | 5 ++--- lib/jobs/downloadfilejob.h | 3 +-- lib/mxcreply.cpp | 7 +++---- lib/mxcreply.h | 6 ++---- lib/networkaccessmanager.cpp | 4 +--- lib/networkaccessmanager.h | 8 +++----- lib/ssosession.cpp | 2 +- lib/ssosession.h | 6 ++---- lib/user.cpp | 4 +--- lib/user.h | 5 ++--- lib/util.h | 41 +++++++++++++++++++++++++++++++++++++++++ 19 files changed, 75 insertions(+), 54 deletions(-) diff --git a/lib/avatar.cpp b/lib/avatar.cpp index 77648562..9304a3de 100644 --- a/lib/avatar.cpp +++ b/lib/avatar.cpp @@ -47,15 +47,11 @@ public: mutable std::vector callbacks; }; -Avatar::Avatar() : d(std::make_unique()) {} +Avatar::Avatar() + : d(makeImpl()) +{} -Avatar::Avatar(QUrl url) : d(std::make_unique(std::move(url))) {} - -Avatar::Avatar(Avatar&&) = default; - -Avatar::~Avatar() = default; - -Avatar& Avatar::operator=(Avatar&&) = default; +Avatar::Avatar(QUrl url) : d(makeImpl(std::move(url))) {} QImage Avatar::get(Connection* connection, int dimension, get_callback_t callback) const diff --git a/lib/avatar.h b/lib/avatar.h index 93f43948..c94dc369 100644 --- a/lib/avatar.h +++ b/lib/avatar.h @@ -3,13 +3,12 @@ #pragma once -#include "quotient_export.h" +#include "util.h" #include #include #include -#include namespace Quotient { class Connection; @@ -18,9 +17,6 @@ class QUOTIENT_API Avatar { public: explicit Avatar(); explicit Avatar(QUrl url); - Avatar(Avatar&&); - ~Avatar(); - Avatar& operator=(Avatar&&); using get_callback_t = std::function; using upload_callback_t = std::function; @@ -41,6 +37,6 @@ public: private: class Private; - std::unique_ptr d; + ImplPtr d; }; } // namespace Quotient diff --git a/lib/connection.cpp b/lib/connection.cpp index 8d1c80f1..1915c2a9 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -246,7 +246,8 @@ public: }; Connection::Connection(const QUrl& server, QObject* parent) - : QObject(parent), d(new Private(std::make_unique(server))) + : QObject(parent) + , d(makeImpl(std::make_unique(server))) { d->q = this; // All d initialization should occur before this line } diff --git a/lib/connection.h b/lib/connection.h index 23062664..b4476347 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -844,7 +844,7 @@ protected Q_SLOTS: private: class Private; - QScopedPointer d; + ImplPtr d; static room_factory_t _roomFactory; static user_factory_t _userFactory; diff --git a/lib/connectiondata.cpp b/lib/connectiondata.cpp index 87ad4577..aca218be 100644 --- a/lib/connectiondata.cpp +++ b/lib/connectiondata.cpp @@ -41,7 +41,7 @@ public: }; ConnectionData::ConnectionData(QUrl baseUrl) - : d(std::make_unique(std::move(baseUrl))) + : d(makeImpl(std::move(baseUrl))) { // Each lambda invocation below takes no more than one job from the // queues (first foreground, then background) and resumes it; then diff --git a/lib/connectiondata.h b/lib/connectiondata.h index e16a2dac..75fc332f 100644 --- a/lib/connectiondata.h +++ b/lib/connectiondata.h @@ -4,9 +4,10 @@ #pragma once +#include "util.h" + #include -#include #include class QNetworkAccessManager; @@ -42,6 +43,6 @@ public: private: class Private; - std::unique_ptr d; + ImplPtr d; }; } // namespace Quotient diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index 971fea7b..f518a1b0 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -194,8 +194,8 @@ BaseJob::BaseJob(HttpVerb verb, const QString& name, QByteArray endpoint, BaseJob::BaseJob(HttpVerb verb, const QString& name, QByteArray endpoint, const QUrlQuery& query, RequestData&& data, bool needsToken) - : d(new Private(verb, std::move(endpoint), query, std::move(data), - needsToken)) + : d(makeImpl(verb, std::move(endpoint), query, std::move(data), + needsToken)) { setObjectName(name); connect(&d->timer, &QTimer::timeout, this, &BaseJob::timeout); diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index f41fc63c..c899170d 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -467,7 +467,7 @@ private: void finishJob(); class Private; - QScopedPointer d; + ImplPtr d; }; inline bool QUOTIENT_API isJobPending(BaseJob* job) diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 6bf221cb..4a507ebd 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -32,13 +32,12 @@ DownloadFileJob::DownloadFileJob(const QString& serverName, const QString& mediaId, const QString& localFilename) : GetContentJob(serverName, mediaId) - , d(localFilename.isEmpty() ? new Private : new Private(localFilename)) + , d(localFilename.isEmpty() ? makeImpl() + : makeImpl(localFilename)) { setObjectName(QStringLiteral("DownloadFileJob")); } -DownloadFileJob::~DownloadFileJob() = default; - QString DownloadFileJob::targetFileName() const { return (d->targetFile ? d->targetFile : d->tempFile)->fileName(); diff --git a/lib/jobs/downloadfilejob.h b/lib/jobs/downloadfilejob.h index 9e807fe7..f8c62e4b 100644 --- a/lib/jobs/downloadfilejob.h +++ b/lib/jobs/downloadfilejob.h @@ -13,13 +13,12 @@ public: DownloadFileJob(const QString& serverName, const QString& mediaId, const QString& localFilename = {}); - ~DownloadFileJob() override; QString targetFileName() const; private: class Private; - QScopedPointer d; + ImplPtr d; void doPrepare() override; void onSentRequest(QNetworkReply* reply) override; diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp index fb16c79f..d3cc3c37 100644 --- a/lib/mxcreply.cpp +++ b/lib/mxcreply.cpp @@ -17,7 +17,7 @@ public: }; MxcReply::MxcReply(QNetworkReply* reply) - : d(std::make_unique(reply)) + : d(makeImpl(reply)) { reply->setParent(this); connect(d->m_reply, &QNetworkReply::finished, this, [this]() { @@ -28,7 +28,7 @@ MxcReply::MxcReply(QNetworkReply* reply) } MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) - : d(std::make_unique(reply)) + : d(makeImpl(reply)) { reply->setParent(this); connect(d->m_reply, &QNetworkReply::finished, this, [this, room, eventId]() { @@ -38,8 +38,6 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId) }); } -MxcReply::~MxcReply() = default; - #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) #define ERROR_SIGNAL errorOccurred #else @@ -47,6 +45,7 @@ MxcReply::~MxcReply() = default; #endif MxcReply::MxcReply() + : d(ZeroImpl()) { static const auto BadRequestPhrase = tr("Bad Request"); QMetaObject::invokeMethod(this, [this]() { diff --git a/lib/mxcreply.h b/lib/mxcreply.h index 1d31d608..f6c4a34d 100644 --- a/lib/mxcreply.h +++ b/lib/mxcreply.h @@ -3,10 +3,9 @@ #pragma once -#include "quotient_export.h" +#include "util.h" #include -#include namespace Quotient { class Room; @@ -18,7 +17,6 @@ public: explicit MxcReply(); explicit MxcReply(QNetworkReply *reply); MxcReply(QNetworkReply* reply, Room* room, const QString& eventId); - ~MxcReply() override; public Q_SLOTS: void abort() override; @@ -28,6 +26,6 @@ protected: private: class Private; - std::unique_ptr d; + ImplPtr d; }; } diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp index 57618329..2c0f716b 100644 --- a/lib/networkaccessmanager.cpp +++ b/lib/networkaccessmanager.cpp @@ -39,7 +39,7 @@ public: }; NetworkAccessManager::NetworkAccessManager(QObject* parent) - : QNetworkAccessManager(parent), d(std::make_unique(this)) + : QNetworkAccessManager(parent), d(makeImpl(this)) {} QList NetworkAccessManager::ignoredSslErrors() const @@ -79,8 +79,6 @@ NetworkAccessManager* NetworkAccessManager::instance() return storage.localData(); } -NetworkAccessManager::~NetworkAccessManager() = default; - QNetworkReply* NetworkAccessManager::createRequest( Operation op, const QNetworkRequest& request, QIODevice* outgoingData) { diff --git a/lib/networkaccessmanager.h b/lib/networkaccessmanager.h index d06f9736..5a9c134c 100644 --- a/lib/networkaccessmanager.h +++ b/lib/networkaccessmanager.h @@ -3,20 +3,18 @@ #pragma once -#include "quotient_export.h" +#include "util.h" #include -#include - namespace Quotient { class Room; class Connection; + class QUOTIENT_API NetworkAccessManager : public QNetworkAccessManager { Q_OBJECT public: NetworkAccessManager(QObject* parent = nullptr); - ~NetworkAccessManager() override; QList ignoredSslErrors() const; void addIgnoredSslError(const QSslError& error); @@ -33,6 +31,6 @@ private: QIODevice* outgoingData = Q_NULLPTR) override; class Private; - std::unique_ptr d; + ImplPtr d; }; } // namespace Quotient diff --git a/lib/ssosession.cpp b/lib/ssosession.cpp index a1d27504..5f3479b8 100644 --- a/lib/ssosession.cpp +++ b/lib/ssosession.cpp @@ -61,7 +61,7 @@ public: SsoSession::SsoSession(Connection* connection, const QString& initialDeviceName, const QString& deviceId) : QObject(connection) - , d(std::make_unique(this, initialDeviceName, deviceId, connection)) + , d(makeImpl(this, initialDeviceName, deviceId, connection)) { qCDebug(MAIN) << "SSO session constructed"; } diff --git a/lib/ssosession.h b/lib/ssosession.h index a658c043..0f3fc3b8 100644 --- a/lib/ssosession.h +++ b/lib/ssosession.h @@ -3,13 +3,11 @@ #pragma once -#include "quotient_export.h" +#include "util.h" #include #include -#include - class QTcpServer; class QTcpSocket; @@ -44,6 +42,6 @@ public: private: class Private; - std::unique_ptr d; + ImplPtr d; }; } // namespace Quotient diff --git a/lib/user.cpp b/lib/user.cpp index 7da71dba..0dbc444a 100644 --- a/lib/user.cpp +++ b/lib/user.cpp @@ -46,7 +46,7 @@ public: decltype(User::Private::otherAvatars) User::Private::otherAvatars {}; User::User(QString userId, Connection* connection) - : QObject(connection), d(new Private(move(userId))) + : QObject(connection), d(makeImpl(move(userId))) { setObjectName(id()); if (connection->userId() == id()) { @@ -61,8 +61,6 @@ Connection* User::connection() const return static_cast(parent()); } -User::~User() = default; - void User::load() { auto* profileJob = diff --git a/lib/user.h b/lib/user.h index 435304ce..8412b7fd 100644 --- a/lib/user.h +++ b/lib/user.h @@ -5,7 +5,7 @@ #pragma once #include "avatar.h" -#include "quotient_export.h" +#include "util.h" #include @@ -27,7 +27,6 @@ class QUOTIENT_API User : public QObject { Q_PROPERTY(QUrl avatarUrl READ avatarUrl NOTIFY defaultAvatarChanged) public: User(QString userId, Connection* connection); - ~User() override; Connection* connection() const; @@ -126,7 +125,7 @@ Q_SIGNALS: private: class Private; - QScopedPointer d; + ImplPtr d; template bool doSetAvatar(SourceT&& source); diff --git a/lib/util.h b/lib/util.h index 399f93c2..f5650ee2 100644 --- a/lib/util.h +++ b/lib/util.h @@ -245,6 +245,47 @@ inline std::pair findFirstOf(InputIt first, InputIt last, return std::make_pair(last, sLast); } +//! \brief An owning implementation pointer +//! +//! This is basically std::unique_ptr<> to hold your pimpl's but without having +//! to define default constructors/operator=() out of line. +//! Thanks to https://oliora.github.io/2015/12/29/pimpl-and-rule-of-zero.html +//! for inspiration +template +using ImplPtr = std::unique_ptr; + +// Why this works (see also the link above): because this defers the moment +// of requiring sizeof of ImplType to the place where makeImpl is invoked +// (which is located, necessarily, in the .cpp file after ImplType definition). +// The stock unique_ptr deleter (std::default_delete) normally needs sizeof +// at the same spot - as long as you defer definition of the owning type +// constructors and operator='s to the .cpp file as well. Which means you +// have to explicitly declare and define them (even if with = default), +// formally breaking the rule of zero; informally, just adding boilerplate code. +// The custom deleter itself is instantiated at makeImpl invocation - there's +// no way earlier to even know how ImplType will be deleted and whether that +// will need sizeof(ImplType) earlier. In theory it's a tad slower because +// the deleter is called by the pointer; however, the difference will not +// be noticeable (if exist at all) for any class with non-trivial contents. + +//! \brief make_unique for ImplPtr +//! +//! Since std::make_unique is not compatible with ImplPtr, this should be used +//! in constructors of frontend classes to create implementation instances. +template +inline ImplPtr makeImpl(ArgTs&&... args) +{ + return ImplPtr { new ImplType(std::forward(args)...), + [](ImplType* impl) { delete impl; } }; +} + +template +const inline ImplPtr ZeroImpl() +{ + return { nullptr, [](ImplType*) {} }; +} + /** Convert what looks like a URL or a Matrix ID to an HTML hyperlink */ QUOTIENT_API void linkifyUrls(QString& htmlEscapedText); -- cgit v1.2.3 From 9fc7bfd5ef1daf84307d553941855fb377fddc7c Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 5 Jan 2022 14:06:15 +0100 Subject: Add a comment, as Sonar advises --- lib/util.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util.h b/lib/util.h index f5650ee2..66db0ece 100644 --- a/lib/util.h +++ b/lib/util.h @@ -283,7 +283,7 @@ inline ImplPtr makeImpl(ArgTs&&... args) template const inline ImplPtr ZeroImpl() { - return { nullptr, [](ImplType*) {} }; + return { nullptr, [](ImplType*) { /* nullptr doesn't need deletion */ } }; } /** Convert what looks like a URL or a Matrix ID to an HTML hyperlink */ -- cgit v1.2.3 From c1bfb156844aa3c53b1588c48ba2002143199510 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 4 Jan 2022 20:47:49 +0100 Subject: .clang-format: slightly tweak penalties With the previous settings clang-format seemed too lenient to characters beyond position 80. [skip ci] --- .clang-format | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.clang-format b/.clang-format index 6e13223e..713d2165 100644 --- a/.clang-format +++ b/.clang-format @@ -104,8 +104,8 @@ PenaltyBreakComment: 45 #PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 200 #PenaltyBreakTemplateDeclaration: 10 -PenaltyExcessCharacter: 20 -PenaltyReturnTypeOnItsOwnLine: 60 +PenaltyExcessCharacter: 40 +PenaltyReturnTypeOnItsOwnLine: 100 #PointerAlignment: Left #ReflowComments: true #SortIncludes: true -- cgit v1.2.3 From ecbff4c1a21ff4c0ab72141bc1a34ae189d33483 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 5 Jan 2022 16:07:25 +0100 Subject: CMakeLists: add install(TARGETS) components again Older CMake versions fail if they don't find those (LGTM uses CMake 3.13 and that one does, at least). --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index c889cf13..fd5f1dca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -316,6 +316,7 @@ endif() # Configure installation install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}Targets + ARCHIVE LIBRARY RUNTIME INCLUDES DESTINATION ${${PROJECT_NAME}_INSTALL_INCLUDEDIR} ) install(DIRECTORY lib/ DESTINATION ${${PROJECT_NAME}_INSTALL_INCLUDEDIR} -- cgit v1.2.3 From 9e3752b8333813b9f00970a1af6e7ca9087ca424 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 5 Jan 2022 14:37:07 +0100 Subject: Thumbnail: drop unneeded constructors Those are already inherited with 'using'. --- lib/events/eventcontent.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index 87ea3672..de9a792b 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -149,10 +149,8 @@ namespace EventContent { */ class QUOTIENT_API Thumbnail : public ImageInfo { public: - Thumbnail() = default; // Allow empty thumbnails - Thumbnail(const QJsonObject& infoJson, const Omittable &file = none); - Thumbnail(const ImageInfo& info) : ImageInfo(info) {} using ImageInfo::ImageInfo; + Thumbnail(const QJsonObject& infoJson, const Omittable &file = none); /** * Writes thumbnail information to "thumbnail_info" subobject -- cgit v1.2.3 From 3986de7f8f1c98f952911c2f93891ea8643df62c Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 5 Jan 2022 15:08:57 +0100 Subject: Make TagRecord generally better It doesn't need all those things inside - order_type alias is no more in use; operator<() is better outside; QLatin1String is better to compare against than const char* (because const char* is assumed to be UTF-8); and TagRecord is really small so it doesn't need const& for parameters. --- lib/events/accountdataevents.h | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h index c0f2202d..12f1f00b 100644 --- a/lib/events/accountdataevents.h +++ b/lib/events/accountdataevents.h @@ -4,27 +4,24 @@ #pragma once #include "event.h" +#include "util.h" namespace Quotient { -constexpr const char* FavouriteTag = "m.favourite"; -constexpr const char* LowPriorityTag = "m.lowpriority"; -constexpr const char* ServerNoticeTag = "m.server_notice"; +constexpr auto FavouriteTag [[maybe_unused]] = "m.favourite"_ls; +constexpr auto LowPriorityTag [[maybe_unused]] = "m.lowpriority"_ls; +constexpr auto ServerNoticeTag [[maybe_unused]] = "m.server_notice"_ls; struct TagRecord { - using order_type = Omittable; - - order_type order; - - TagRecord(order_type order = none) : order(order) {} - - bool operator<(const TagRecord& other) const - { - // Per The Spec, rooms with no order should be after those with order, - // against std::optional<>::operator<() convention. - return order && (!other.order || *order < *other.order); - } + Omittable order = none; }; +inline bool operator<(TagRecord lhs, TagRecord rhs) +{ + // Per The Spec, rooms with no order should be after those with order, + // against std::optional<>::operator<() convention. + return lhs.order && (!rhs.order || *lhs.order < *rhs.order); +} + template <> struct JsonObjectConverter { static void fillFrom(const QJsonObject& jo, TagRecord& rec) @@ -41,7 +38,7 @@ struct JsonObjectConverter { rec.order = none; } } - static void dumpTo(QJsonObject& jo, const TagRecord& rec) + static void dumpTo(QJsonObject& jo, TagRecord rec) { addParam(jo, QStringLiteral("order"), rec.order); } -- cgit v1.2.3 From 9351d9afcbaae0bdc8aa26f7361be1f84cac7467 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 5 Jan 2022 15:13:24 +0100 Subject: DEFINE_EVENT_TYPEID: fix up deprecation warnings --- lib/events/event.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/events/event.h b/lib/events/event.h index f12e525e..858972da 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -264,9 +264,9 @@ using Events = EventsArray; // provide matrixTypeId() and typeId(). #define DEFINE_EVENT_TYPEID(Id_, Type_) \ static constexpr event_type_t TypeId = Id_##_ls; \ - [[deprecated("Use _Type::TypeId directly instead")]] \ + [[deprecated("Use " #Type_ "::TypeId directly instead")]] \ static constexpr event_mtype_t matrixTypeId() { return Id_; } \ - [[deprecated("Use _Type::TypeId directly instead")]] \ + [[deprecated("Use " #Type_ "::TypeId directly instead")]] \ static event_type_t typeId() { return TypeId; } \ // End of macro -- cgit v1.2.3 From c483ef95bd3e5effd6dd08c6d0ea0f83d0aec051 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 5 Jan 2022 15:14:33 +0100 Subject: Fully-qualify types passed to slots --- lib/jobs/basejob.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index c899170d..1567e635 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -248,7 +248,7 @@ public: } public Q_SLOTS: - void initiate(ConnectionData* connData, bool inBackground); + void initiate(Quotient::ConnectionData* connData, bool inBackground); /** * Abandons the result of this job, arrived or unarrived. -- cgit v1.2.3 From 703e5e8c1b48bea7bd82906967cb7651f7e96751 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 5 Jan 2022 15:53:21 +0100 Subject: Cleanup Room::pinnedEvents() Use 'auto'; range-for instead of an iterator loop. --- lib/room.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index edf5dcd9..55efb5b9 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -562,16 +562,14 @@ QStringList Room::pinnedEventIds() const { return d->getCurrentState()->pinnedEvents(); } -QVector< const Quotient::RoomEvent* > Quotient::Room::pinnedEvents() const +QVector Quotient::Room::pinnedEvents() const { - QStringList events = d->getCurrentState()->pinnedEvents(); + const auto& pinnedIds = d->getCurrentState()->pinnedEvents(); QVector pinnedEvents; - QStringList::iterator i; - for (i = events.begin(); i != events.end(); ++i) { - auto timelineItem = findInTimeline(*i); - if (timelineItem != historyEdge()) - pinnedEvents.append(timelineItem->event()); - } + for (auto&& evtId: pinnedIds) + if (const auto& it = findInTimeline(evtId); it != historyEdge()) + pinnedEvents.append(it->event()); + return pinnedEvents; } -- cgit v1.2.3 From bc4a0f5d408d901f3c8f4dfeec0574ded04845bf Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 5 Jan 2022 20:13:44 +0100 Subject: Brush up SsoSession; document Connection::prepareForSso Although parented to Connection, SsoSession was pretty leaky in that unsuccessful login attempts didn't delete the object and in some errors didn't even close the local HTTP socket (though new connections would no more be accepted). Also, without the documentation it wasn't clear who owns the object returned by Connection::prepareForSso(). Now it is. Unfortunately, it's not easy to cover SsoSession with tests. Basically, it takes a homeserver and a mock "SSO agent" that would check the SSO URL for validity and then both send the login authorisation to the homeserver as well as ping the callback given by SsoSession. Maybe for another time. --- lib/connection.h | 11 +++++++++++ lib/ssosession.cpp | 42 +++++++++++++++++++++--------------------- lib/ssosession.h | 6 ++---- 3 files changed, 34 insertions(+), 25 deletions(-) diff --git a/lib/connection.h b/lib/connection.h index b4476347..28688cc1 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -443,6 +443,17 @@ public: std::forward(jobArgs)...); } + //! \brief Start a local HTTP server and generate a single sign-on URL + //! + //! This call does the preparatory steps to carry out single sign-on + //! sequence + //! \sa https://matrix.org/docs/guides/sso-for-client-developers + //! \return A proxy object holding two URLs: one for SSO on the chosen + //! homeserver and another for the local callback address. Normally + //! you won't need the callback URL unless you proxy the response + //! with a custom UI. You do not need to delete the SsoSession + //! object; the Connection that issued it will dispose of it once + //! the login sequence completes (with any outcome). Q_INVOKABLE SsoSession* prepareForSso(const QString& initialDeviceName, const QString& deviceId = {}); diff --git a/lib/ssosession.cpp b/lib/ssosession.cpp index 5f3479b8..93e252cc 100644 --- a/lib/ssosession.cpp +++ b/lib/ssosession.cpp @@ -15,10 +15,10 @@ using namespace Quotient; class SsoSession::Private { public: - Private(SsoSession* q, const QString& initialDeviceName = {}, - const QString& deviceId = {}, Connection* connection = nullptr) - : initialDeviceName(initialDeviceName) - , deviceId(deviceId) + Private(SsoSession* q, QString initialDeviceName = {}, + QString deviceId = {}, Connection* connection = nullptr) + : initialDeviceName(std::move(initialDeviceName)) + , deviceId(std::move(deviceId)) , connection(connection) { auto* server = new QTcpServer(q); @@ -29,7 +29,7 @@ public: .arg(server->serverPort()); ssoUrl = connection->getUrlForApi(callbackUrl); - QObject::connect(server, &QTcpServer::newConnection, q, [this, server] { + QObject::connect(server, &QTcpServer::newConnection, q, [this, q, server] { qCDebug(MAIN) << "SSO callback initiated"; socket = server->nextPendingConnection(); server->close(); @@ -43,8 +43,14 @@ public: }); QObject::connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater); + QObject::connect(socket, &QObject::destroyed, q, + &QObject::deleteLater); }); + qCDebug(MAIN) << "SSO session constructed"; } + ~Private() { qCDebug(MAIN) << "SSO session deconstructed"; } + Q_DISABLE_COPY_MOVE(Private) + void processCallback(); void sendHttpResponse(const QByteArray& code, const QByteArray& msg); void onError(const QByteArray& code, const QString& errorMsg); @@ -62,14 +68,7 @@ SsoSession::SsoSession(Connection* connection, const QString& initialDeviceName, const QString& deviceId) : QObject(connection) , d(makeImpl(this, initialDeviceName, deviceId, connection)) -{ - qCDebug(MAIN) << "SSO session constructed"; -} - -SsoSession::~SsoSession() -{ - qCDebug(MAIN) << "SSO session deconstructed"; -} +{} QUrl SsoSession::ssoUrl() const { return d->ssoUrl; } @@ -82,29 +81,29 @@ void SsoSession::Private::processCallback() // (see at https://github.com/clementine-player/Clementine/) const auto& requestParts = requestData.split(' '); if (requestParts.size() < 2 || requestParts[1].isEmpty()) { - onError("400 Bad Request", tr("No login token in SSO callback")); + onError("400 Bad Request", tr("Malformed single sign-on callback")); return; } const auto& QueryItemName = QStringLiteral("loginToken"); QUrlQuery query { QUrl(requestParts[1]).query() }; if (!query.hasQueryItem(QueryItemName)) { - onError("400 Bad Request", tr("Malformed single sign-on callback")); + onError("400 Bad Request", tr("No login token in SSO callback")); + return; } qCDebug(MAIN) << "Found the token in SSO callback, logging in"; connection->loginWithToken(query.queryItemValue(QueryItemName).toLatin1(), initialDeviceName, deviceId); connect(connection, &Connection::connected, socket, [this] { - const QString msg = - "The application '" % QCoreApplication::applicationName() - % "' has successfully logged in as a user " % connection->userId() - % " with device id " % connection->deviceId() - % ". This window can be closed. Thank you.\r\n"; + const auto msg = + tr("The application '%1' has successfully logged in as a user %2 " + "with device id %3. This window can be closed. Thank you.\r\n") + .arg(QCoreApplication::applicationName(), connection->userId(), + connection->deviceId()); sendHttpResponse("200 OK", msg.toHtmlEscaped().toUtf8()); socket->disconnectFromHost(); }); connect(connection, &Connection::loginError, socket, [this] { onError("401 Unauthorised", tr("Login failed")); - socket->disconnectFromHost(); }); } @@ -128,4 +127,5 @@ void SsoSession::Private::onError(const QByteArray& code, // [kitsune] Yeah, I know, dirty. Maybe the "right" way would be to have // an intermediate signal but that seems just a fight for purity. emit connection->loginError(errorMsg, requestData); + socket->disconnectFromHost(); } diff --git a/lib/ssosession.h b/lib/ssosession.h index 0f3fc3b8..e6a3f8fb 100644 --- a/lib/ssosession.h +++ b/lib/ssosession.h @@ -8,9 +8,6 @@ #include #include -class QTcpServer; -class QTcpSocket; - namespace Quotient { class Connection; @@ -36,7 +33,8 @@ class QUOTIENT_API SsoSession : public QObject { public: SsoSession(Connection* connection, const QString& initialDeviceName, const QString& deviceId = {}); - ~SsoSession() override; + ~SsoSession() override = default; + QUrl ssoUrl() const; QUrl callbackUrl() const; -- cgit v1.2.3 From bd280a087ecab30f94f7937513ee298c233fcba1 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 18 Jan 2022 08:54:52 +0100 Subject: Revise inline keyword usage - Templates and constexpr imply inline - A function called from a single site better be inlined. --- lib/events/event.h | 2 +- lib/jobs/basejob.h | 2 +- lib/quotient_common.h | 8 +++----- lib/uri.cpp | 2 +- lib/util.h | 8 ++++---- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/events/event.h b/lib/events/event.h index 858972da..f10f6a8d 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -311,7 +311,7 @@ inline auto switchOnType(const BaseEventT& event, FnT&& fn) namespace _impl { // Using bool instead of auto below because auto apparently upsets MSVC template - inline constexpr bool needs_downcast = + constexpr bool needs_downcast = std::is_base_of_v>> && !std::is_same_v>>; } diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index 1567e635..9ed58ba8 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -28,7 +28,7 @@ class QUOTIENT_API BaseJob : public QObject { static QByteArray encodeIfParam(const QString& paramPart); template - static inline auto encodeIfParam(const char (&constPart)[N]) + static auto encodeIfParam(const char (&constPart)[N]) { return constPart; } diff --git a/lib/quotient_common.h b/lib/quotient_common.h index 3d38ce1f..af3e2730 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -77,7 +77,7 @@ enum class Membership : unsigned int { }; QUO_DECLARE_FLAGS_NS(MembershipMask, Membership) -constexpr inline auto MembershipStrings = make_array( +constexpr auto MembershipStrings = make_array( // The order MUST be the same as the order in the original enum "join", "leave", "invite", "knock", "ban"); @@ -95,7 +95,7 @@ enum class JoinState : std::underlying_type_t { }; QUO_DECLARE_FLAGS_NS(JoinStates, JoinState) -constexpr inline auto JoinStateStrings = make_array( +constexpr auto JoinStateStrings = make_array( MembershipStrings[0], MembershipStrings[1], MembershipStrings[2], MembershipStrings[3] /* same as MembershipStrings, sans "ban" */ ); @@ -125,9 +125,7 @@ enum RoomType { }; Q_ENUM_NS(RoomType) -constexpr inline auto RoomTypeStrings = make_array( - "m.space" -); +constexpr auto RoomTypeStrings = make_array("m.space"); } // namespace Quotient Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::MembershipMask) diff --git a/lib/uri.cpp b/lib/uri.cpp index c8843dda..3ce81a21 100644 --- a/lib/uri.cpp +++ b/lib/uri.cpp @@ -75,7 +75,7 @@ static auto decodeFragmentPart(QStringView part) return QUrl::fromPercentEncoding(part.toLatin1()).toUtf8(); } -static auto matrixToUrlRegexInit() +static inline auto matrixToUrlRegexInit() { // See https://matrix.org/docs/spec/appendices#matrix-to-navigation const QRegularExpression MatrixToUrlRE { diff --git a/lib/util.h b/lib/util.h index 66db0ece..3505b62f 100644 --- a/lib/util.h +++ b/lib/util.h @@ -54,9 +54,9 @@ using UnorderedMap = std::unordered_map>; namespace _impl { template - constexpr inline auto IsOmittableValue = false; + constexpr auto IsOmittableValue = false; template - constexpr inline auto IsOmittable = IsOmittableValue>; + constexpr auto IsOmittable = IsOmittableValue>; } constexpr auto none = std::nullopt; @@ -165,7 +165,7 @@ Omittable(T&&) -> Omittable; namespace _impl { template - constexpr inline auto IsOmittableValue> = true; + constexpr auto IsOmittableValue> = true; } template @@ -191,7 +191,7 @@ inline auto merge(T1& lhs, const Omittable& rhs) return true; } -inline constexpr auto operator"" _ls(const char* s, std::size_t size) +constexpr auto operator"" _ls(const char* s, std::size_t size) { return QLatin1String(s, int(size)); } -- cgit v1.2.3 From aa2465bb7991876c3b5afb5afda54f43fefd9e0a Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 6 Jan 2022 12:04:42 +0100 Subject: .clang-format: try harder to avoid return type on its own line In reality it's often not just the return type but the return type and the preceding attribute, in particular [[deprecated("...")]] - and unfortunately this attribute (and any other, actually) cannot be given its own line using ClangFormat. So at least try to glue those two to the function name as much as possible... --- .clang-format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.clang-format b/.clang-format index 713d2165..8375204a 100644 --- a/.clang-format +++ b/.clang-format @@ -105,7 +105,7 @@ PenaltyBreakComment: 45 PenaltyBreakString: 200 #PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 40 -PenaltyReturnTypeOnItsOwnLine: 100 +PenaltyReturnTypeOnItsOwnLine: 150 #PointerAlignment: Left #ReflowComments: true #SortIncludes: true -- cgit v1.2.3 From aaa57e0e458435e23047fcb325872f0350171bad Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 14 Jan 2022 22:37:28 +0100 Subject: AccountRegistry: derive from QVector and clean up Notably, Quotient::AccountRegistry::instance() is now deprecated in favour of Quotient::Accounts inline variable. --- lib/accountregistry.cpp | 73 +++++++++++++------------------------------- lib/accountregistry.h | 49 ++++++++++++++++++----------- lib/connection.cpp | 4 +-- lib/networkaccessmanager.cpp | 2 +- 4 files changed, 55 insertions(+), 73 deletions(-) diff --git a/lib/accountregistry.cpp b/lib/accountregistry.cpp index a292ed45..616b54b4 100644 --- a/lib/accountregistry.cpp +++ b/lib/accountregistry.cpp @@ -8,90 +8,59 @@ using namespace Quotient; -void AccountRegistry::add(Connection* c) +void AccountRegistry::add(Connection* a) { - if (m_accounts.contains(c)) + if (contains(a)) return; - beginInsertRows(QModelIndex(), m_accounts.size(), m_accounts.size()); - m_accounts += c; + beginInsertRows(QModelIndex(), size(), size()); + push_back(a); endInsertRows(); } -void AccountRegistry::drop(Connection* c) +void AccountRegistry::drop(Connection* a) { - beginRemoveRows(QModelIndex(), m_accounts.indexOf(c), m_accounts.indexOf(c)); - m_accounts.removeOne(c); + const auto idx = indexOf(a); + beginRemoveRows(QModelIndex(), idx, idx); + remove(idx); endRemoveRows(); - Q_ASSERT(!m_accounts.contains(c)); + Q_ASSERT(!contains(a)); } bool AccountRegistry::isLoggedIn(const QString &userId) const { - return std::any_of(m_accounts.cbegin(), m_accounts.cend(), - [&userId](Connection* a) { return a->userId() == userId; }); + return std::any_of(cbegin(), cend(), [&userId](const Connection* a) { + return a->userId() == userId; + }); } -bool AccountRegistry::contains(Connection *c) const +QVariant AccountRegistry::data(const QModelIndex& index, int role) const { - return m_accounts.contains(c); -} - -AccountRegistry::AccountRegistry() = default; - -QVariant AccountRegistry::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) { - return {}; - } - - if (index.row() >= m_accounts.count()) { + if (!index.isValid() || index.row() >= count()) return {}; - } - auto account = m_accounts[index.row()]; - - if (role == ConnectionRole) { - return QVariant::fromValue(account); - } + if (role == AccountRole) + return QVariant::fromValue(at(index.row())); return {}; } -int AccountRegistry::rowCount(const QModelIndex &parent) const +int AccountRegistry::rowCount(const QModelIndex& parent) const { - if (parent.isValid()) { - return 0; - } - - return m_accounts.count(); + return parent.isValid() ? 0 : count(); } QHash AccountRegistry::roleNames() const { - return {{ConnectionRole, "connection"}}; + return { { AccountRole, "connection" } }; } -bool AccountRegistry::isEmpty() const -{ - return m_accounts.isEmpty(); -} -int AccountRegistry::count() const -{ - return m_accounts.count(); -} - -const QVector AccountRegistry::accounts() const -{ - return m_accounts; -} Connection* AccountRegistry::get(const QString& userId) { - for (const auto &connection : m_accounts) { - if(connection->userId() == userId) { + for (const auto &connection : *this) { + if (connection->userId() == userId) return connection; - } } return nullptr; } diff --git a/lib/accountregistry.h b/lib/accountregistry.h index f7a864df..2f6dffdf 100644 --- a/lib/accountregistry.h +++ b/lib/accountregistry.h @@ -6,42 +6,55 @@ #include "quotient_export.h" -#include -#include #include namespace Quotient { class Connection; -class QUOTIENT_API AccountRegistry : public QAbstractListModel { +class QUOTIENT_API AccountRegistry : public QAbstractListModel, + private QVector { Q_OBJECT public: + using const_iterator = QVector::const_iterator; + using const_reference = QVector::const_reference; + enum EventRoles { - ConnectionRole = Qt::UserRole + 1, + AccountRole = Qt::UserRole + 1, + ConnectionRole = AccountRole }; - static AccountRegistry &instance() { - static AccountRegistry _instance; - return _instance; - } + [[deprecated("Use Accounts variable instead")]] // + static AccountRegistry& instance(); + + // Expose most of QVector's const-API but only provide add() and drop() + // for changing it. In theory other changing operations could be supported + // too; but then boilerplate begin/end*() calls has to be tucked into each + // and this class gives no guarantees on the order of entries, so why care. - const QVector accounts() const; + const QVector& accounts() const { return *this; } void add(Connection* a); void drop(Connection* a); + const_iterator begin() const { return QVector::begin(); } + const_iterator end() const { return QVector::end(); } + const_reference front() const { return QVector::front(); } + const_reference back() const { return QVector::back(); } bool isLoggedIn(const QString& userId) const; - bool isEmpty() const; - int count() const; - bool contains(Connection*) const; Connection* get(const QString& userId); - [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; + using QVector::isEmpty, QVector::empty; + using QVector::size, QVector::count, QVector::capacity; + using QVector::cbegin, QVector::cend, QVector::contains; + + // QAbstractItemModel interface implementation + [[nodiscard]] QVariant data(const QModelIndex& index, + int role) const override; + [[nodiscard]] int rowCount( + const QModelIndex& parent = QModelIndex()) const override; [[nodiscard]] QHash roleNames() const override; +}; -private: - AccountRegistry(); +inline QUOTIENT_API AccountRegistry Accounts {}; - QVector m_accounts; -}; +inline AccountRegistry& AccountRegistry::instance() { return Accounts; } } diff --git a/lib/connection.cpp b/lib/connection.cpp index 1915c2a9..67bc83f9 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -258,7 +258,7 @@ Connection::~Connection() { qCDebug(MAIN) << "deconstructing connection object for" << userId(); stopSync(); - AccountRegistry::instance().drop(this); + Accounts.drop(this); } void Connection::resolveServer(const QString& mxid) @@ -438,7 +438,7 @@ void Connection::Private::completeSetup(const QString& mxId) qCDebug(MAIN) << "Using server" << data->baseUrl().toDisplayString() << "by user" << data->userId() << "from device" << data->deviceId(); - AccountRegistry::instance().add(q); + Accounts.add(q); #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; #else // Quotient_E2EE_ENABLED diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp index 2c0f716b..58c3cc3a 100644 --- a/lib/networkaccessmanager.cpp +++ b/lib/networkaccessmanager.cpp @@ -97,7 +97,7 @@ QNetworkReply* NetworkAccessManager::createRequest( // TODO: Make the best effort with a direct unauthenticated request // to the media server } else { - auto* const connection = AccountRegistry::instance().get(accountId); + auto* const connection = Accounts.get(accountId); if (!connection) { qCWarning(NETWORK) << "Connection" << accountId << "not found"; return new MxcReply(); -- cgit v1.2.3 From b17b629df44dbc888f6b6bee79d0d13d662371cf Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 18 Jan 2022 08:55:10 +0100 Subject: Add [[maybe_unused]] to things the lib doesn't use --- lib/function_traits.cpp | 2 +- lib/quotient_common.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/function_traits.cpp b/lib/function_traits.cpp index 20bcf30e..6542101a 100644 --- a/lib/function_traits.cpp +++ b/lib/function_traits.cpp @@ -47,7 +47,7 @@ static_assert(std::is_same_v, int>, "Test fn_arg_t defaulting to first argument"); template -static void ft(const std::vector&); +[[maybe_unused]] static void ft(const std::vector&); static_assert( std::is_same)>, const std::vector&>(), "Test function templates"); diff --git a/lib/quotient_common.h b/lib/quotient_common.h index af3e2730..02a9f0cd 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -95,7 +95,7 @@ enum class JoinState : std::underlying_type_t { }; QUO_DECLARE_FLAGS_NS(JoinStates, JoinState) -constexpr auto JoinStateStrings = make_array( +[[maybe_unused]] constexpr auto JoinStateStrings = make_array( MembershipStrings[0], MembershipStrings[1], MembershipStrings[2], MembershipStrings[3] /* same as MembershipStrings, sans "ban" */ ); @@ -125,7 +125,7 @@ enum RoomType { }; Q_ENUM_NS(RoomType) -constexpr auto RoomTypeStrings = make_array("m.space"); +[[maybe_unused]] constexpr auto RoomTypeStrings = make_array("m.space"); } // namespace Quotient Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::MembershipMask) -- cgit v1.2.3 From 1010c8d0009293a6ae357c23e8b4732302b6b8bd Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 18 Jan 2022 06:42:42 +0100 Subject: Drop unused forward declarations --- lib/networkaccessmanager.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/networkaccessmanager.h b/lib/networkaccessmanager.h index 5a9c134c..8ff1c6b5 100644 --- a/lib/networkaccessmanager.h +++ b/lib/networkaccessmanager.h @@ -8,8 +8,6 @@ #include namespace Quotient { -class Room; -class Connection; class QUOTIENT_API NetworkAccessManager : public QNetworkAccessManager { Q_OBJECT -- cgit v1.2.3 From c7907084282c7957d085acb329574ab6a7d593c8 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 18 Jan 2022 11:43:32 +0100 Subject: Move over non-interface code to QLatin1String It's better than const char* because any interaction between const char* and QString assumes that const char* contains UTF-8, which is pessimistic and therefore inefficient; at the same time: - construction of QString from QLatin1String is extremely fast (boiling down to padding null bytes) - "something"_ls is much shorter than QStringLiteral("something") - "something"_ls produces a direct pointer to the literal at compile time, using the benefits of raw string literals (deduplication, e.g.) The library API will also transition to QLatin1String where applicable, likely in 0.8. --- lib/events/roommessageevent.cpp | 64 ++++++++++++++++++++--------------------- lib/settings.cpp | 9 +++--- lib/uri.cpp | 19 ++++++------ 3 files changed, 47 insertions(+), 45 deletions(-) diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index 2b7b4166..0f58d8a6 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -19,15 +19,13 @@ using namespace EventContent; using MsgType = RoomMessageEvent::MsgType; -static const auto RelatesToKeyL = "m.relates_to"_ls; -static const auto MsgTypeKeyL = "msgtype"_ls; -static const auto FormattedBodyKeyL = "formatted_body"_ls; - -static const auto TextTypeKey = "m.text"; -static const auto EmoteTypeKey = "m.emote"; -static const auto NoticeTypeKey = "m.notice"; - -static const auto HtmlContentTypeId = QStringLiteral("org.matrix.custom.html"); +static constexpr auto RelatesToKey = "m.relates_to"_ls; +static constexpr auto MsgTypeKey = "msgtype"_ls; +static constexpr auto FormattedBodyKey = "formatted_body"_ls; +static constexpr auto TextTypeKey = "m.text"_ls; +static constexpr auto EmoteTypeKey = "m.emote"_ls; +static constexpr auto NoticeTypeKey = "m.notice"_ls; +static constexpr auto HtmlContentTypeId = "org.matrix.custom.html"_ls; template TypedBase* make(const QJsonObject& json) @@ -38,13 +36,13 @@ TypedBase* make(const QJsonObject& json) template <> TypedBase* make(const QJsonObject& json) { - return json.contains(FormattedBodyKeyL) || json.contains(RelatesToKeyL) + return json.contains(FormattedBodyKey) || json.contains(RelatesToKey) ? new TextContent(json) : nullptr; } struct MsgTypeDesc { - QString matrixType; + QLatin1String matrixType; MsgType enumType; TypedBase* (*maker)(const QJsonObject&); }; @@ -53,11 +51,11 @@ const std::vector msgTypes = { { TextTypeKey, MsgType::Text, make }, { EmoteTypeKey, MsgType::Emote, make }, { NoticeTypeKey, MsgType::Notice, make }, - { QStringLiteral("m.image"), MsgType::Image, make }, - { QStringLiteral("m.file"), MsgType::File, make }, - { QStringLiteral("m.location"), MsgType::Location, make }, - { QStringLiteral("m.video"), MsgType::Video, make }, - { QStringLiteral("m.audio"), MsgType::Audio, make } + { "m.image"_ls, MsgType::Image, make }, + { "m.file"_ls, MsgType::File, make }, + { "m.location"_ls, MsgType::Location, make }, + { "m.video"_ls, MsgType::Video, make }, + { "m.audio"_ls, MsgType::Audio, make } }; QString msgTypeToJson(MsgType enumType) @@ -94,12 +92,12 @@ QJsonObject RoomMessageEvent::assembleContentJson(const QString& plainBody, TypedBase* content) { auto json = content ? content->toJson() : QJsonObject(); - if (json.contains(RelatesToKeyL)) { + if (json.contains(RelatesToKey)) { if (jsonMsgType != TextTypeKey && jsonMsgType != NoticeTypeKey && jsonMsgType != EmoteTypeKey) { - json.remove(RelatesToKeyL); + json.remove(RelatesToKey); qCWarning(EVENTS) - << RelatesToKeyL << "cannot be used in" << jsonMsgType + << RelatesToKey << "cannot be used in" << jsonMsgType << "messages; the relation has been stripped off"; } else { // After the above, we know for sure that the content is TextContent @@ -109,9 +107,9 @@ QJsonObject RoomMessageEvent::assembleContentJson(const QString& plainBody, if (textContent->relatesTo->type == RelatesTo::ReplacementTypeId()) { auto newContentJson = json.take("m.new_content"_ls).toObject(); newContentJson.insert(BodyKey, plainBody); - newContentJson.insert(MsgTypeKeyL, jsonMsgType); + newContentJson.insert(MsgTypeKey, jsonMsgType); json.insert(QStringLiteral("m.new_content"), newContentJson); - json[MsgTypeKeyL] = jsonMsgType; + json[MsgTypeKey] = jsonMsgType; json[BodyKeyL] = "* " + plainBody; return json; } @@ -177,8 +175,8 @@ RoomMessageEvent::RoomMessageEvent(const QJsonObject& obj) if (isRedacted()) return; const QJsonObject content = contentJson(); - if (content.contains(MsgTypeKeyL) && content.contains(BodyKeyL)) { - auto msgtype = content[MsgTypeKeyL].toString(); + if (content.contains(MsgTypeKey) && content.contains(BodyKeyL)) { + auto msgtype = content[MsgTypeKey].toString(); bool msgTypeFound = false; for (const auto& mt : msgTypes) if (mt.matrixType == msgtype) { @@ -204,7 +202,7 @@ RoomMessageEvent::MsgType RoomMessageEvent::msgtype() const QString RoomMessageEvent::rawMsgtype() const { - return contentPart(MsgTypeKeyL); + return contentPart(MsgTypeKey); } QString RoomMessageEvent::plainBody() const @@ -295,7 +293,7 @@ Omittable fromJson(const QJsonValue& jv) } // namespace Quotient TextContent::TextContent(const QJsonObject& json) - : relatesTo(fromJson>(json[RelatesToKeyL])) + : relatesTo(fromJson>(json[RelatesToKey])) { QMimeDatabase db; static const auto PlainTextMimeType = db.mimeTypeForName("text/plain"); @@ -308,7 +306,7 @@ TextContent::TextContent(const QJsonObject& json) // of sending HTML messages. if (actualJson["format"_ls].toString() == HtmlContentTypeId) { mimeType = HtmlMimeType; - body = actualJson[FormattedBodyKeyL].toString(); + body = actualJson[FormattedBodyKey].toString(); } else { // Falling back to plain text, as there's no standard way to describe // rich text in messages. @@ -320,7 +318,6 @@ TextContent::TextContent(const QJsonObject& json) void TextContent::fillJson(QJsonObject* json) const { static const auto FormatKey = QStringLiteral("format"); - static const auto FormattedBodyKey = QStringLiteral("formatted_body"); Q_ASSERT(json); if (mimeType.inherits("text/html")) { @@ -328,11 +325,14 @@ void TextContent::fillJson(QJsonObject* json) const json->insert(FormattedBodyKey, body); } if (relatesTo) { - json->insert(QStringLiteral("m.relates_to"), - relatesTo->type == RelatesTo::ReplyTypeId() ? - QJsonObject { { relatesTo->type, QJsonObject{ { EventIdKey, relatesTo->eventId } } } } : - QJsonObject { { "rel_type", relatesTo->type }, { EventIdKey, relatesTo->eventId } } - ); + json->insert( + QStringLiteral("m.relates_to"), + relatesTo->type == RelatesTo::ReplyTypeId() + ? QJsonObject { { relatesTo->type, + QJsonObject { + { EventIdKey, relatesTo->eventId } } } } + : QJsonObject { { "rel_type", relatesTo->type }, + { EventIdKey, relatesTo->eventId } }); if (relatesTo->type == RelatesTo::ReplacementTypeId()) { QJsonObject newContentJson; if (mimeType.inherits("text/html")) { diff --git a/lib/settings.cpp b/lib/settings.cpp index 5549e4de..20734a7e 100644 --- a/lib/settings.cpp +++ b/lib/settings.cpp @@ -3,6 +3,7 @@ #include "settings.h" +#include "util.h" #include "logging.h" #include @@ -109,10 +110,10 @@ QUO_DEFINE_SETTING(AccountSettings, QString, deviceName, "device_name", {}, QUO_DEFINE_SETTING(AccountSettings, bool, keepLoggedIn, "keep_logged_in", false, setKeepLoggedIn) -static const auto HomeserverKey = QStringLiteral("homeserver"); -static const auto AccessTokenKey = QStringLiteral("access_token"); -static const auto EncryptionAccountPickleKey = - QStringLiteral("encryption_account_pickle"); +static constexpr auto HomeserverKey = "homeserver"_ls; +static constexpr auto AccessTokenKey = "access_token"_ls; +static constexpr auto EncryptionAccountPickleKey = + "encryption_account_pickle"_ls; QUrl AccountSettings::homeserver() const { diff --git a/lib/uri.cpp b/lib/uri.cpp index 3ce81a21..11c59b69 100644 --- a/lib/uri.cpp +++ b/lib/uri.cpp @@ -3,27 +3,28 @@ #include "uri.h" +#include "util.h" #include "logging.h" #include using namespace Quotient; -struct ReplacePair { QByteArray uriString; char sigil; }; +struct ReplacePair { QLatin1String uriString; char sigil; }; /// \brief Defines bi-directional mapping of path prefixes and sigils /// /// When there are two prefixes for the same sigil, the first matching /// entry for a given sigil is used. -static const auto replacePairs = { - ReplacePair { "u/", '@' }, - { "user/", '@' }, - { "roomid/", '!' }, - { "r/", '#' }, - { "room/", '#' }, +static const ReplacePair replacePairs[] = { + { "u/"_ls, '@' }, + { "user/"_ls, '@' }, + { "roomid/"_ls, '!' }, + { "r/"_ls, '#' }, + { "room/"_ls, '#' }, // The notation for bare event ids is not proposed in MSC2312 but there's // https://github.com/matrix-org/matrix-doc/pull/2644 - { "e/", '$' }, - { "event/", '$' } + { "e/"_ls, '$' }, + { "event/"_ls, '$' } }; Uri::Uri(QByteArray primaryId, QByteArray secondaryId, QString query) -- cgit v1.2.3 From bf82aeea369cacfc93a0e6d6d9feb01f1f2afdb2 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 18 Jan 2022 11:43:21 +0100 Subject: Don't use 'static' on top-level/namespace scope When internal linkage is necessary, anonymous namespaces fulfil the same purpose in a better way. See also: https://stackoverflow.com/questions/4422507/superiority-of-unnamed-namespace-over-static --- lib/converters.h | 2 +- lib/events/roommessageevent.cpp | 18 +++++++++++------- lib/settings.cpp | 9 +++++---- lib/uri.cpp | 6 +++++- 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/lib/converters.h b/lib/converters.h index a6028f1b..8eea1cd3 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -355,7 +355,7 @@ namespace _impl { }; } // namespace _impl -static constexpr bool IfNotEmpty = false; +constexpr bool IfNotEmpty = false; /*! Add a key-value pair to QJsonObject or QUrlQuery * diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index 0f58d8a6..5ab0f845 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -19,13 +19,15 @@ using namespace EventContent; using MsgType = RoomMessageEvent::MsgType; -static constexpr auto RelatesToKey = "m.relates_to"_ls; -static constexpr auto MsgTypeKey = "msgtype"_ls; -static constexpr auto FormattedBodyKey = "formatted_body"_ls; -static constexpr auto TextTypeKey = "m.text"_ls; -static constexpr auto EmoteTypeKey = "m.emote"_ls; -static constexpr auto NoticeTypeKey = "m.notice"_ls; -static constexpr auto HtmlContentTypeId = "org.matrix.custom.html"_ls; +namespace { // Supporting internal definitions + +constexpr auto RelatesToKey = "m.relates_to"_ls; +constexpr auto MsgTypeKey = "msgtype"_ls; +constexpr auto FormattedBodyKey = "formatted_body"_ls; +constexpr auto TextTypeKey = "m.text"_ls; +constexpr auto EmoteTypeKey = "m.emote"_ls; +constexpr auto NoticeTypeKey = "m.notice"_ls; +constexpr auto HtmlContentTypeId = "org.matrix.custom.html"_ls; template TypedBase* make(const QJsonObject& json) @@ -87,6 +89,8 @@ inline bool isReplacement(const Omittable& rel) return rel && rel->type == RelatesTo::ReplacementTypeId(); } +} // anonymous namespace + QJsonObject RoomMessageEvent::assembleContentJson(const QString& plainBody, const QString& jsonMsgType, TypedBase* content) diff --git a/lib/settings.cpp b/lib/settings.cpp index 20734a7e..2491d89d 100644 --- a/lib/settings.cpp +++ b/lib/settings.cpp @@ -110,10 +110,11 @@ QUO_DEFINE_SETTING(AccountSettings, QString, deviceName, "device_name", {}, QUO_DEFINE_SETTING(AccountSettings, bool, keepLoggedIn, "keep_logged_in", false, setKeepLoggedIn) -static constexpr auto HomeserverKey = "homeserver"_ls; -static constexpr auto AccessTokenKey = "access_token"_ls; -static constexpr auto EncryptionAccountPickleKey = - "encryption_account_pickle"_ls; +namespace { +constexpr auto HomeserverKey = "homeserver"_ls; +constexpr auto AccessTokenKey = "access_token"_ls; +constexpr auto EncryptionAccountPickleKey = "encryption_account_pickle"_ls; +} QUrl AccountSettings::homeserver() const { diff --git a/lib/uri.cpp b/lib/uri.cpp index 11c59b69..6b7d1d20 100644 --- a/lib/uri.cpp +++ b/lib/uri.cpp @@ -10,12 +10,14 @@ using namespace Quotient; +namespace { + struct ReplacePair { QLatin1String uriString; char sigil; }; /// \brief Defines bi-directional mapping of path prefixes and sigils /// /// When there are two prefixes for the same sigil, the first matching /// entry for a given sigil is used. -static const ReplacePair replacePairs[] = { +const ReplacePair replacePairs[] = { { "u/"_ls, '@' }, { "user/"_ls, '@' }, { "roomid/"_ls, '!' }, @@ -27,6 +29,8 @@ static const ReplacePair replacePairs[] = { { "event/"_ls, '$' } }; +} + Uri::Uri(QByteArray primaryId, QByteArray secondaryId, QString query) { if (primaryId.isEmpty()) -- cgit v1.2.3 From e1ffc58ab9abf6321f92a6b648d6f8da08b0924d Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 19 Jan 2022 13:33:32 +0100 Subject: CMakeLists: Bring back ARCHIVE DESTINATION in install() The older CMake used by LGTM is still unhappy without it. (See also recent changes to this file.) --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fd5f1dca..c4e97673 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -316,7 +316,8 @@ endif() # Configure installation install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}Targets - ARCHIVE LIBRARY RUNTIME + LIBRARY RUNTIME + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} INCLUDES DESTINATION ${${PROJECT_NAME}_INSTALL_INCLUDEDIR} ) install(DIRECTORY lib/ DESTINATION ${${PROJECT_NAME}_INSTALL_INCLUDEDIR} -- cgit v1.2.3 From fdff209744ac4c422f63fe2549aa0132df7e6292 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 21 Jan 2022 02:04:10 +0100 Subject: Redo EventRelation; deprecate RelatesTo RelatesTo and EventRelation have been two means to the same end in two different contexts. (Modernised) EventRelation is the one used now both for ReactionEvent and EventContent::TextContent. The modernisation mostly boils down to using inline variables instead of functions to return relation types and switching to QLatin1String from const char* (because we know exactly that those constants are Latin-1 and QLatin1String is more efficient than const char* to compare/convert to QString). --- CMakeLists.txt | 3 ++- lib/events/eventrelation.cpp | 38 ++++++++++++++++++++++++++++++ lib/events/eventrelation.h | 52 +++++++++++++++++++++++++++++++++++++++++ lib/events/reactionevent.cpp | 29 ----------------------- lib/events/reactionevent.h | 32 ++----------------------- lib/events/roommessageevent.cpp | 38 ++++++++---------------------- lib/events/roommessageevent.h | 27 +++++++++++---------- lib/room.cpp | 11 ++++----- lib/room.h | 5 ++-- quotest/quotest.cpp | 2 +- 10 files changed, 128 insertions(+), 109 deletions(-) create mode 100644 lib/events/eventrelation.cpp create mode 100644 lib/events/eventrelation.h delete mode 100644 lib/events/reactionevent.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fd5f1dca..df193b94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -153,6 +153,7 @@ list(APPEND lib_SRCS lib/events/stateevent.h lib/events/stateevent.cpp lib/events/simplestateevents.h lib/events/eventcontent.h lib/events/eventcontent.cpp + lib/events/eventrelation.h lib/events/eventrelation.cpp lib/events/roomcreateevent.h lib/events/roomcreateevent.cpp lib/events/roomtombstoneevent.h lib/events/roomtombstoneevent.cpp lib/events/roommessageevent.h lib/events/roommessageevent.cpp @@ -162,7 +163,7 @@ list(APPEND lib_SRCS lib/events/typingevent.h lib/events/typingevent.cpp lib/events/accountdataevents.h lib/events/receiptevent.h lib/events/receiptevent.cpp - lib/events/reactionevent.h lib/events/reactionevent.cpp + lib/events/reactionevent.h lib/events/callinviteevent.h lib/events/callinviteevent.cpp lib/events/callcandidatesevent.h lib/events/callanswerevent.h lib/events/callanswerevent.cpp diff --git a/lib/events/eventrelation.cpp b/lib/events/eventrelation.cpp new file mode 100644 index 00000000..04972f45 --- /dev/null +++ b/lib/events/eventrelation.cpp @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2022 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "eventrelation.h" + +#include "../logging.h" +#include "event.h" + +using namespace Quotient; + +void JsonObjectConverter::dumpTo(QJsonObject& jo, + const EventRelation& pod) +{ + if (pod.type.isEmpty()) { + qCWarning(MAIN) << "Empty relation type; won't dump to JSON"; + return; + } + jo.insert(RelTypeKey, pod.type); + jo.insert(EventIdKey, pod.eventId); + if (pod.type == EventRelation::AnnotationType) + jo.insert(QStringLiteral("key"), pod.key); +} + +void JsonObjectConverter::fillFrom(const QJsonObject& jo, + EventRelation& pod) +{ + if (const auto replyJson = jo.value(EventRelation::ReplyType).toObject(); + !replyJson.isEmpty()) { + pod.type = EventRelation::ReplyType; + fromJson(replyJson[EventIdKeyL], pod.eventId); + } else { + // The experimental logic for generic relationships (MSC1849) + fromJson(jo[RelTypeKey], pod.type); + fromJson(jo[EventIdKeyL], pod.eventId); + if (pod.type == EventRelation::AnnotationType) + fromJson(jo["key"_ls], pod.key); + } +} diff --git a/lib/events/eventrelation.h b/lib/events/eventrelation.h new file mode 100644 index 00000000..e445ee42 --- /dev/null +++ b/lib/events/eventrelation.h @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2022 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "converters.h" + +namespace Quotient { + +[[maybe_unused]] constexpr auto RelatesToKey = "m.relates_to"_ls; +constexpr auto RelTypeKey = "rel_type"_ls; + +struct QUOTIENT_API EventRelation { + using reltypeid_t = QLatin1String; + + QString type; + QString eventId; + QString key = {}; // Only used for m.annotation for now + + static constexpr auto ReplyType = "m.in_reply_to"_ls; + static constexpr auto AnnotationType = "m.annotation"_ls; + static constexpr auto ReplacementType = "m.replace"_ls; + + static EventRelation replyTo(QString eventId) + { + return { ReplyType, std::move(eventId) }; + } + static EventRelation annotate(QString eventId, QString key) + { + return { AnnotationType, std::move(eventId), std::move(key) }; + } + static EventRelation replace(QString eventId) + { + return { ReplacementType, std::move(eventId) }; + } + + [[deprecated("Use ReplyRelation variable instead")]] + static constexpr auto Reply() { return ReplyType; } + [[deprecated("Use AnnotationRelation variable instead")]] // + static constexpr auto Annotation() { return AnnotationType; } + [[deprecated("Use ReplacementRelation variable instead")]] // + static constexpr auto Replacement() { return ReplacementType; } +}; + +template <> +struct QUOTIENT_API JsonObjectConverter { + static void dumpTo(QJsonObject& jo, const EventRelation& pod); + static void fillFrom(const QJsonObject& jo, EventRelation& pod); +}; + +} + diff --git a/lib/events/reactionevent.cpp b/lib/events/reactionevent.cpp deleted file mode 100644 index b53fffd6..00000000 --- a/lib/events/reactionevent.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Kitsune Ral -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "reactionevent.h" - -using namespace Quotient; - -void JsonObjectConverter::dumpTo( - QJsonObject& jo, const EventRelation& pod) -{ - if (pod.type.isEmpty()) { - qCWarning(MAIN) << "Empty relation type; won't dump to JSON"; - return; - } - jo.insert(QStringLiteral("rel_type"), pod.type); - jo.insert(EventIdKey, pod.eventId); - if (pod.type == EventRelation::Annotation()) - jo.insert(QStringLiteral("key"), pod.key); -} - -void JsonObjectConverter::fillFrom( - const QJsonObject& jo, EventRelation& pod) -{ - // The experimental logic for generic relationships (MSC1849) - fromJson(jo["rel_type"_ls], pod.type); - fromJson(jo[EventIdKeyL], pod.eventId); - if (pod.type == EventRelation::Annotation()) - fromJson(jo["key"_ls], pod.key); -} diff --git a/lib/events/reactionevent.h b/lib/events/reactionevent.h index ce11eaed..b3cb3ca7 100644 --- a/lib/events/reactionevent.h +++ b/lib/events/reactionevent.h @@ -4,38 +4,10 @@ #pragma once #include "roomevent.h" +#include "eventrelation.h" namespace Quotient { -struct QUOTIENT_API EventRelation { - using reltypeid_t = const char*; - static constexpr reltypeid_t Reply() { return "m.in_reply_to"; } - static constexpr reltypeid_t Annotation() { return "m.annotation"; } - static constexpr reltypeid_t Replacement() { return "m.replace"; } - - QString type; - QString eventId; - QString key = {}; // Only used for m.annotation for now - - static EventRelation replyTo(QString eventId) - { - return { Reply(), std::move(eventId) }; - } - static EventRelation annotate(QString eventId, QString key) - { - return { Annotation(), std::move(eventId), std::move(key) }; - } - static EventRelation replace(QString eventId) - { - return { Replacement(), std::move(eventId) }; - } -}; -template <> -struct QUOTIENT_API JsonObjectConverter { - static void dumpTo(QJsonObject& jo, const EventRelation& pod); - static void fillFrom(const QJsonObject& jo, EventRelation& pod); -}; - class QUOTIENT_API ReactionEvent : public RoomEvent { public: DEFINE_EVENT_TYPEID("m.reaction", ReactionEvent) @@ -47,7 +19,7 @@ public: explicit ReactionEvent(const QJsonObject& obj) : RoomEvent(typeId(), obj) {} EventRelation relation() const { - return contentPart("m.relates_to"_ls); + return contentPart(RelatesToKey); } }; REGISTER_EVENT_TYPE(ReactionEvent) diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index 5ab0f845..c07a4f3c 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -6,6 +6,7 @@ #include "roommessageevent.h" #include "logging.h" +#include "events/eventrelation.h" #include #include @@ -20,7 +21,6 @@ using namespace EventContent; using MsgType = RoomMessageEvent::MsgType; namespace { // Supporting internal definitions - constexpr auto RelatesToKey = "m.relates_to"_ls; constexpr auto MsgTypeKey = "msgtype"_ls; constexpr auto FormattedBodyKey = "formatted_body"_ls; @@ -84,9 +84,9 @@ MsgType jsonToMsgType(const QString& matrixType) return MsgType::Unknown; } -inline bool isReplacement(const Omittable& rel) +inline bool isReplacement(const Omittable& rel) { - return rel && rel->type == RelatesTo::ReplacementTypeId(); + return rel && rel->type == EventRelation::ReplacementType; } } // anonymous namespace @@ -105,10 +105,10 @@ QJsonObject RoomMessageEvent::assembleContentJson(const QString& plainBody, << "messages; the relation has been stripped off"; } else { // After the above, we know for sure that the content is TextContent - // and that its RelatesTo structure is not omitted + // and that its EventRelation structure is not omitted auto* textContent = static_cast(content); Q_ASSERT(textContent && textContent->relatesTo.has_value()); - if (textContent->relatesTo->type == RelatesTo::ReplacementTypeId()) { + if (textContent->relatesTo->type == EventRelation::ReplacementType) { auto newContentJson = json.take("m.new_content"_ls).toObject(); newContentJson.insert(BodyKey, plainBody); newContentJson.insert(MsgTypeKey, jsonMsgType); @@ -269,7 +269,7 @@ QString RoomMessageEvent::rawMsgTypeForFile(const QFileInfo& fi) } TextContent::TextContent(QString text, const QString& contentType, - Omittable relatesTo) + Omittable relatesTo) : mimeType(QMimeDatabase().mimeTypeForName(contentType)) , body(std::move(text)) , relatesTo(std::move(relatesTo)) @@ -278,26 +278,8 @@ TextContent::TextContent(QString text, const QString& contentType, mimeType = QMimeDatabase().mimeTypeForName("text/html"); } -namespace Quotient { -// Overload the default fromJson<> logic that defined in converters.h -// as we want -template <> -Omittable fromJson(const QJsonValue& jv) -{ - const auto jo = jv.toObject(); - if (jo.isEmpty()) - return none; - const auto replyJson = jo.value(RelatesTo::ReplyTypeId()).toObject(); - if (!replyJson.isEmpty()) - return replyTo(fromJson(replyJson[EventIdKeyL])); - - return RelatesTo { jo.value("rel_type"_ls).toString(), - jo.value(EventIdKeyL).toString() }; -} -} // namespace Quotient - TextContent::TextContent(const QJsonObject& json) - : relatesTo(fromJson>(json[RelatesToKey])) + : relatesTo(fromJson>(json[RelatesToKey])) { QMimeDatabase db; static const auto PlainTextMimeType = db.mimeTypeForName("text/plain"); @@ -331,13 +313,13 @@ void TextContent::fillJson(QJsonObject* json) const if (relatesTo) { json->insert( QStringLiteral("m.relates_to"), - relatesTo->type == RelatesTo::ReplyTypeId() + relatesTo->type == EventRelation::ReplyType ? QJsonObject { { relatesTo->type, QJsonObject { { EventIdKey, relatesTo->eventId } } } } - : QJsonObject { { "rel_type", relatesTo->type }, + : QJsonObject { { RelTypeKey, relatesTo->type }, { EventIdKey, relatesTo->eventId } }); - if (relatesTo->type == RelatesTo::ReplacementTypeId()) { + if (relatesTo->type == EventRelation::ReplacementType) { QJsonObject newContentJson; if (mimeType.inherits("text/html")) { newContentJson.insert(FormatKey, HtmlContentTypeId); diff --git a/lib/events/roommessageevent.h b/lib/events/roommessageevent.h index 0c901b7a..44ef05fb 100644 --- a/lib/events/roommessageevent.h +++ b/lib/events/roommessageevent.h @@ -6,6 +6,7 @@ #pragma once #include "eventcontent.h" +#include "eventrelation.h" #include "roomevent.h" class QFileInfo; @@ -97,23 +98,25 @@ REGISTER_EVENT_TYPE(RoomMessageEvent) using MessageEventType = RoomMessageEvent::MsgType; namespace EventContent { - // Additional event content types - struct RelatesTo { - static constexpr const char* ReplyTypeId() { return "m.in_reply_to"; } - static constexpr const char* ReplacementTypeId() { return "m.replace"; } - QString type; // The only supported relation so far - QString eventId; + struct [[deprecated("Use Quotient::EventRelation instead")]] RelatesTo + : EventRelation { + static constexpr auto ReplyTypeId() { return Reply(); } + static constexpr auto ReplacementTypeId() { return Replacement(); } }; - inline RelatesTo replyTo(QString eventId) + [[deprecated("Use EventRelation::replyTo() instead")]] + inline auto replyTo(QString eventId) { - return { RelatesTo::ReplyTypeId(), std::move(eventId) }; + return EventRelation::replyTo(std::move(eventId)); } - inline RelatesTo replacementOf(QString eventId) + [[deprecated("Use EventRelation::replace() instead")]] + inline auto replacementOf(QString eventId) { - return { RelatesTo::ReplacementTypeId(), std::move(eventId) }; + return EventRelation::replace(std::move(eventId)); } + // Additional event content types + /** * Rich text content for m.text, m.emote, m.notice * @@ -123,14 +126,14 @@ namespace EventContent { class QUOTIENT_API TextContent : public TypedBase { public: TextContent(QString text, const QString& contentType, - Omittable relatesTo = none); + Omittable relatesTo = none); explicit TextContent(const QJsonObject& json); QMimeType type() const override { return mimeType; } QMimeType mimeType; QString body; - Omittable relatesTo; + Omittable relatesTo; protected: void fillJson(QJsonObject* json) const override; diff --git a/lib/room.cpp b/lib/room.cpp index 55efb5b9..ba63f50d 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -996,14 +996,14 @@ Room::findPendingEvent(const QString& txnId) const }); } -const Room::RelatedEvents Room::relatedEvents(const QString& evtId, - const char* relType) const +const Room::RelatedEvents Room::relatedEvents( + const QString& evtId, EventRelation::reltypeid_t relType) const { return d->relations.value({ evtId, relType }); } -const Room::RelatedEvents Room::relatedEvents(const RoomEvent& evt, - const char* relType) const +const Room::RelatedEvents Room::relatedEvents( + const RoomEvent& evt, EventRelation::reltypeid_t relType) const { return relatedEvents(evt.id(), relType); } @@ -2506,8 +2506,7 @@ bool Room::Private::processRedaction(const RedactionEvent& redaction) } if (const auto* reaction = eventCast(oldEvent)) { const auto& targetEvtId = reaction->relation().eventId; - const auto lookupKey = - qMakePair(targetEvtId, EventRelation::Annotation()); + const QPair lookupKey { targetEvtId, EventRelation::AnnotationType }; if (relations.contains(lookupKey)) { relations[lookupKey].removeOne(reaction); emit q->updatedEvent(targetEvtId); diff --git a/lib/room.h b/lib/room.h index 61f475e8..15bc7648 100644 --- a/lib/room.h +++ b/lib/room.h @@ -21,6 +21,7 @@ #include "events/roommessageevent.h" #include "events/roomcreateevent.h" #include "events/roomtombstoneevent.h" +#include "events/eventrelation.h" #include #include @@ -394,9 +395,9 @@ public: PendingEvents::const_iterator findPendingEvent(const QString& txnId) const; const RelatedEvents relatedEvents(const QString& evtId, - const char* relType) const; + EventRelation::reltypeid_t relType) const; const RelatedEvents relatedEvents(const RoomEvent& evt, - const char* relType) const; + EventRelation::reltypeid_t relType) const; const RoomCreateEvent* creation() const { diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index 5a5642fe..40bc456b 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -381,7 +381,7 @@ TEST_IMPL(sendReaction) if (actualTargetEvtId != targetEvtId) return false; const auto reactions = targetRoom->relatedEvents( - targetEvtId, EventRelation::Annotation()); + targetEvtId, EventRelation::AnnotationType); // It's a test room, assuming no interference there should // be exactly one reaction if (reactions.size() != 1) -- cgit v1.2.3 From 0689028f4a0db403a55c6158e750fee3ba6c7098 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 20 Jan 2022 17:35:28 +0100 Subject: Refactor assembleContentJson() Get rid of that Q_ASSERT() in the middle that only worked in Debug builds anyway. --- lib/events/roommessageevent.cpp | 43 +++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index c07a4f3c..d63352cb 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -95,32 +95,33 @@ QJsonObject RoomMessageEvent::assembleContentJson(const QString& plainBody, const QString& jsonMsgType, TypedBase* content) { - auto json = content ? content->toJson() : QJsonObject(); - if (json.contains(RelatesToKey)) { + QJsonObject json; + if (content) { + // TODO: replace with content->fillJson(json) when it starts working + json = content->toJson(); if (jsonMsgType != TextTypeKey && jsonMsgType != NoticeTypeKey && jsonMsgType != EmoteTypeKey) { - json.remove(RelatesToKey); - qCWarning(EVENTS) - << RelatesToKey << "cannot be used in" << jsonMsgType - << "messages; the relation has been stripped off"; - } else { - // After the above, we know for sure that the content is TextContent - // and that its EventRelation structure is not omitted - auto* textContent = static_cast(content); - Q_ASSERT(textContent && textContent->relatesTo.has_value()); - if (textContent->relatesTo->type == EventRelation::ReplacementType) { - auto newContentJson = json.take("m.new_content"_ls).toObject(); - newContentJson.insert(BodyKey, plainBody); - newContentJson.insert(MsgTypeKey, jsonMsgType); - json.insert(QStringLiteral("m.new_content"), newContentJson); - json[MsgTypeKey] = jsonMsgType; - json[BodyKeyL] = "* " + plainBody; - return json; + if (json.contains(RelatesToKey)) { + json.remove(RelatesToKey); + qCWarning(EVENTS) + << RelatesToKey << "cannot be used in" << jsonMsgType + << "messages; the relation has been stripped off"; } + } else if (auto* textContent = static_cast(content); + textContent->relatesTo + && textContent->relatesTo->type + == EventRelation::ReplacementType) { + auto newContentJson = json.take("m.new_content"_ls).toObject(); + newContentJson.insert(BodyKey, plainBody); + newContentJson.insert(MsgTypeKey, jsonMsgType); + json.insert(QStringLiteral("m.new_content"), newContentJson); + json[MsgTypeKey] = jsonMsgType; + json[BodyKeyL] = "* " + plainBody; + return json; } } - json.insert(QStringLiteral("msgtype"), jsonMsgType); - json.insert(QStringLiteral("body"), plainBody); + json.insert(MsgTypeKey, jsonMsgType); + json.insert(BodyKey, plainBody); return json; } -- cgit v1.2.3 From 63c953800330017ebb2afbabf41e5c4932c4d640 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 22 Jan 2022 13:43:15 +0100 Subject: Quotest: fix changeName test This test was very unreliable because memberRenamed() signal was emitted upon lazy-loading of past member renames. The new code connects to aboutToAddNewMessages() instead, which should only contain the latest renaming event (past events are delivered in the 'state' object). --- quotest/quotest.cpp | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index 40bc456b..792faabd 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -14,6 +14,7 @@ #include "events/reactionevent.h" #include "events/redactionevent.h" #include "events/simplestateevents.h" +#include "events/roommemberevent.h" #include #include @@ -592,25 +593,31 @@ TEST_IMPL(changeName) clog << "Renaming the user to " << newName.toStdString() << " in the target room" << endl; localUser->rename(newName, targetRoom); - connectUntil(targetRoom, &Room::memberRenamed, this, - [this, thisTest, localUser, newName](const User* u) { - if (localUser != u) - return false; - if (localUser->name(targetRoom) != newName) - FAIL_TEST(); - - clog - << "Member rename successful, renaming the account" + connectUntil( + targetRoom, &Room::aboutToAddNewMessages, this, + [this, thisTest, localUser, newName](const RoomEventsRange& evts) { + for (const auto& e : evts) { + if (const auto* rme = eventCast(e)) { + if (rme->stateKey() != localUser->id() + || !rme->isRename()) + continue; + if (!rme->newDisplayName() + || *rme->newDisplayName() != newName) + FAIL_TEST(); + clog << "Member rename successful, renaming the account" << endl; - const auto newN = newName.mid(0, 5); - localUser->rename(newN); - connectUntil(localUser, &User::defaultNameChanged, - this, [this, thisTest, localUser, newN] { - targetRoom->localUser()->rename({}); - FINISH_TEST(localUser->name() == newN); - }); - return true; - }); + const auto newN = newName.mid(0, 5); + localUser->rename(newN); + connectUntil(localUser, &User::defaultNameChanged, this, + [this, thisTest, localUser, newN] { + targetRoom->localUser()->rename({}); + FINISH_TEST(localUser->name() == newN); + }); + return true; + } + } + return false; + }); }); return false; } -- cgit v1.2.3 From cc9908e5159ed93a18eda9f9794a7c9fc7f67f27 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 23 Jan 2022 10:34:38 +0100 Subject: Fix visit() return type It's too restrictive compared to switchOnType() overloads and doesn't map to the case with a default value. --- lib/events/event.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/events/event.h b/lib/events/event.h index f10f6a8d..113fa3fa 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -354,8 +354,7 @@ switchOnType(const BaseT& event, FnT1&& fn1, FnT2&& fn2, FnTs&&... fns) template [[deprecated("The new name for visit() is switchOnType()")]] // -inline std::common_type_t...> -visit(const BaseT& event, FnTs&&... fns) +inline auto visit(const BaseT& event, FnTs&&... fns) { return switchOnType(event, std::forward(fns)...); } -- cgit v1.2.3 From abbab8d8f8c566bc2c9cdf766c6fbb11d978ca47 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 23 Jan 2022 17:11:22 +0100 Subject: Omittable: split out from util.h and refresh Improvements: - Quotient::lift() - a way to invoke a function on an optional (including Omittable) or a pointer if it's 'truthy'. Doesn't need enhanced function_traits<>, only the standard library; works on any number of arguments that can be dereferenced and casted to bool. - then() - the version of lift() as a member function. - edit() was renamed to ensure() (edit() might become a read-write counterpart of then() at some point). It's not really used across libQuotient codebase (or elsewhere) but is staying there just in case. It can also accept an initializer, removing the requirement of default-constructibility. - Quotient::merge() is simplified, with one universal implementation covering both Omittable/optional and plain values. - All that now lives in its dedicated pair of files, further decluttering util.h --- CMakeLists.txt | 1 + gtad/gtad.yaml | 2 +- lib/connection.h | 1 + lib/converters.h | 1 + lib/omittable.cpp | 34 +++++++++ lib/omittable.h | 223 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/util.h | 141 ---------------------------------- 7 files changed, 261 insertions(+), 142 deletions(-) create mode 100644 lib/omittable.cpp create mode 100644 lib/omittable.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 599424ab..13c8f591 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,6 +127,7 @@ list(APPEND lib_SRCS lib/quotient_common.h lib/quotient_export.h lib/function_traits.h lib/function_traits.cpp + lib/omittable.h lib/omittable.cpp lib/networkaccessmanager.h lib/networkaccessmanager.cpp lib/connectiondata.h lib/connectiondata.cpp lib/connection.h lib/connection.cpp 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..e7bc6898 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -3,6 +3,7 @@ #pragma once +#include "omittable.h" #include "util.h" #include diff --git a/lib/omittable.cpp b/lib/omittable.cpp new file mode 100644 index 00000000..245ae721 --- /dev/null +++ b/lib/omittable.cpp @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2021 Kitsune Ral +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "omittable.h" + +// Omittable<> tests +using namespace Quotient; + +Omittable testFn(bool) { return 0; } +bool testFn2(int) { return false; } +static_assert( + std::is_same_v>().then(testFn)), + Omittable>); +static_assert( + std::is_same_v< + decltype(std::declval>().then_or(testFn, 0)), int>); +static_assert( + std::is_same_v>().then(testFn)), + Omittable>); +static_assert(std::is_same_v>() + .then(testFn2) + .then(testFn)), + Omittable>); +static_assert(std::is_same_v>() + .then(testFn) + .then_or(testFn2, false)), + bool>); + +constexpr auto visitTestFn(int, bool) { return false; } +static_assert( + std::is_same_v, decltype(lift(testFn2, Omittable()))>); +static_assert(std::is_same_v, + decltype(lift(visitTestFn, Omittable(), + Omittable()))>); 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 +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include + +namespace Quotient { + +template +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. (... && bool(args)) == true). +//! \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 +inline auto lift(FnT&& fn, MaybeTs&&... args) +{ + return (... && bool(args)) + ? Omittable(std::invoke(std::forward(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 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 innermost; }; + * struct Outer { int anotherMember = 10; Omittable inner; }; + * Omittable 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 +class Omittable : public std::optional { +public: + using base_type = std::optional; + using value_type = std::decay_t; + + using std::optional::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 + value_type& ensure(U&& defaultValue = value_type {}) + { + return this->has_value() ? this->operator*() + : this->emplace(std::forward(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 + auto merge(const std::optional& other) + -> std::enable_if_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\. If the current Omittable is empty, + //! the invocation is skipped altogether and Omittable\{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 const T&) + //! returning Omittable, T2 is any supported type + //! \sa then_or, transform + template + auto then(FnT&& fn) const& + { + return lift(std::forward(fn), *this); + } + + //! \brief Lift a callable into the rvalue Omittable + //! + //! This is an rvalue overload for then(). + template + auto then(FnT&& fn) && + { + return lift(std::forward(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 + auto then_or(FnT&& fn, FallbackT&& fallback) const& + { + return then(std::forward(fn)) + .value_or(std::forward(fallback)); + } + + //! \brief Lift a callable into the rvalue Omittable, with a fallback + //! + //! This is an overload for functions that accept rvalue + template + auto then_or(FnT&& fn, FallbackT&& fallback) && + { + return then(std::forward(fn)) + .value_or(std::forward(fallback)); + } +}; + +template +Omittable(T&&) -> Omittable; + +//! \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 +inline auto merge(T1& lhs, const std::optional& rhs) + -> std::enable_if_t, bool> +{ + if (!rhs || lhs == *rhs) + return false; + lhs = *rhs; + return true; +} + +} // namespace Quotient 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 #include -#include #include #include -#include #ifndef Q_DISABLE_MOVE // Q_DISABLE_MOVE was introduced in Q_VERSION_CHECK(5,13,0) @@ -52,145 +50,6 @@ struct HashQ { template using UnorderedMap = std::unordered_map>; -namespace _impl { - template - constexpr auto IsOmittableValue = false; - template - constexpr auto IsOmittable = IsOmittableValue>; -} - -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 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 innermost; }; - * struct Outer { int anotherMember = 10; Omittable inner; }; - * Omittable 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 -class Omittable : public std::optional { -public: - using base_type = std::optional; - using value_type = std::decay_t; - - using std::optional::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 - auto merge(const Omittable& other) - -> std::enable_if_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 -Omittable(T&&) -> Omittable; - -namespace _impl { - template - constexpr auto IsOmittableValue> = true; -} - -template -inline auto merge(Omittable& lhs, T2&& rhs) -{ - return lhs.merge(std::forward(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 -inline auto merge(T1& lhs, const Omittable& rhs) - -> std::enable_if_t - && std::is_convertible_v, 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)); -- cgit v1.2.3 From 29775218e0c8b6c176015dd3128a0d545906ae6f Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 2 Dec 2021 20:54:39 +0100 Subject: Cleanup some #includes --- lib/events/eventcontent.cpp | 1 - lib/events/receiptevent.cpp | 1 - lib/events/roomevent.cpp | 1 - lib/events/roommemberevent.cpp | 1 - lib/jobs/basejob.cpp | 2 -- lib/jobs/basejob.h | 6 +++--- 6 files changed, 3 insertions(+), 9 deletions(-) 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 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/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 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 #include -#include #include #include #include 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 #include -- cgit v1.2.3 From 1215888438b901ad80e8bf9882cdcc6a3f057399 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 4 Dec 2021 20:12:59 +0100 Subject: converters.h: slightly clearer code in _impl::addTo --- lib/converters.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/converters.h b/lib/converters.h index e7bc6898..8ddf6e45 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -288,6 +288,8 @@ QVariantHash QUOTIENT_API fromJson(const QJsonValue& jv); // Conditional insertion into a QJsonObject +constexpr bool IfNotEmpty = false; + namespace _impl { template inline void addTo(QJsonObject& o, const QString& k, ValT&& v) @@ -333,7 +335,7 @@ namespace _impl { // This one is for types that have isEmpty() when Force is false template - struct AddNode().isEmpty())> { + struct AddNode().isEmpty())> { template static void impl(ContT& container, const QString& key, ForwardedT&& value) @@ -343,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 - struct AddNode, false> { + struct AddNode, IfNotEmpty> { template static void impl(ContT& container, const QString& key, const OmittableT& value) @@ -356,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 -- cgit v1.2.3 From 8183a33c130f1284404edc61767ff6d29402d200 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 23 Jan 2022 17:11:45 +0100 Subject: RoomStateView This class is called to provide an arbitrary snapshot of a room state; as the first step, Room::currentState() returns an instance of this class that stores, well, the current state. Implelementation-wise it's the same hash map of two-part state event keys to const event pointers; however, RoomStateView provides additional operations: - get(), that deprecates Room::getCurrentState(), returns a pointer to a particular event if the current state has it. Unlike the original method, the pointer returned from this one can be nullptr; this is done to get rid of stubbed state events that have to be created everytime a "state miss" occurred (i.e., when getCurrentState() does not find an existing event in the current state). - eventsOfType() - this is a new place for Room::stateEventsOfType() introduced recently. - query() - this is a way to specify a piece of the state content that you need to retrieve by passing a member function or a function object that retrieves it. That is especially convenient with member functions of the event class; just pass the pointer to this member function, and query() will parse the event type it has to retrieve out of it and call that member function on the event object. Returns an Omittable<>; if the respective piece of state doesn't exist, you'll get `Quotient::none` (the same as `std::nullopt`). - queryOr() - the same but with the fallback value; instead of an Omittable<>, the fallback value will be returned if the needed event is not found. --- CMakeLists.txt | 1 + lib/room.cpp | 150 ++++++++++++++++++++++++-------------------------- lib/room.h | 54 ++++++++---------- lib/roomstateview.cpp | 35 ++++++++++++ lib/roomstateview.h | 127 ++++++++++++++++++++++++++++++++++++++++++ lib/user.cpp | 17 ++++-- 6 files changed, 268 insertions(+), 116 deletions(-) create mode 100644 lib/roomstateview.cpp create mode 100644 lib/roomstateview.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 13c8f591..ddf11680 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,6 +134,7 @@ list(APPEND lib_SRCS 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/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 currentState; + RoomStateView currentState; /// Servers with aliases for this room except the one of the local user /// \sa Room::remoteAliases QSet aliasServers; @@ -227,34 +228,6 @@ public: return evt; } - QVector stateEventsOfType(const QString& evtType) const - { - auto vals = QVector(); - for (auto it = currentState.cbegin(); it != currentState.cend(); ++it) - if (it.key().first == evtType) - vals.append(it.value()); - - return vals; - } - - template - 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(evt); - } - -// template -// 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 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()->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()->predecessor().roomId; + if (const auto* evt = currentState().get()) + 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()->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()->name(); + return currentState().queryOr(&RoomNameEvent::name, QString()); } QStringList Room::aliases() const { - const auto* evt = d->getCurrentState(); - auto result = evt->altAliases(); - if (!evt->alias().isEmpty()) - result << evt->alias(); - return result; + if (const auto* evt = currentState().get()) { + auto result = evt->altAliases(); + if (!evt->alias().isEmpty()) + result << evt->alias(); + return result; + } + return {}; } QStringList Room::altAliases() const { - return d->getCurrentState()->altAliases(); + return currentState().queryOr(&RoomCanonicalAliasEvent::altAliases, + QStringList()); } QString Room::canonicalAlias() const { - return d->getCurrentState()->alias(); + return currentState().queryOr(&RoomCanonicalAliasEvent::alias, QString()); } QString Room::displayName() const { return d->displayname; } QStringList Room::pinnedEventIds() const { - return d->getCurrentState()->pinnedEvents(); + return currentState().queryOr(&RoomPinnedEvent::pinnedEvents, QStringList()); } QVector Quotient::Room::pinnedEvents() const { - const auto& pinnedIds = d->getCurrentState()->pinnedEvents(); QVector 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()->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(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()) { - const auto currentUserLevel = plEvt->powerLevelForUser(localUser()->id()); + if (const auto* plEvt = currentState().get()) { + 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(); +} + +const RoomTombstoneEvent *Room::tombstone() const +{ + return currentState().get(); +} + 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()->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 -Room::stateEventsOfType(const QString& evtType) const -{ - return d->stateEventsOfType(evtType); -} - -const QHash& 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(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( - 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(mxId); - return rme->newDisplayName() ? *rme->newDisplayName() - : rme->prevContent() ? rme->prevContent()->displayName.value_or(QString()) - : QString(); + if (const auto rme = currentState().get(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(mxId); - return rme->newAvatarUrl() ? *rme->newAvatarUrl() - : rme->prevContent() ? rme->prevContent()->avatarUrl.value_or(QUrl()) - : QUrl(); + if (const auto rme = currentState().get(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(); - } - const RoomTombstoneEvent* tombstone() const - { - return getCurrentState(); - } + 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& 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 - 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 + [[deprecated("Use currentState().get() instead; " + "make sure to check its result for nullptrs")]] // const EvT* getCurrentState(const QString& stateKey = {}) const { - const auto* evt = - eventCast(getCurrentState(EvT::matrixTypeId(), stateKey)); + QT_IGNORE_DEPRECATIONS( + const auto* evt = eventCast( + 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 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 +// 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 +RoomStateView::eventsOfType(const QString& evtType) const +{ + auto vals = QVector(); + 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 +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "events/stateevent.h" + +#include + +namespace Quotient { + +class Room; + +class RoomStateView : private QHash { + Q_GADGET +public: + const QHash& 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 + const EvT* get(const QString& stateKey = {}) const + { + static_assert(std::is_base_of_v); + if (const auto* evt = get(EvT::matrixTypeId(), stateKey)) { + Q_ASSERT(evt->matrixType() == EvT::matrixTypeId() + && evt->stateKey() == stateKey); + return eventCast(evt); + } + return nullptr; + } + + using QHash::contains; + + bool contains(const QString& evtType, const QString& stateKey = {}) const; + + template + 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 + //! 'content' 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 + eventsOfType(const QString& evtType) const; + + template + auto query(const QString& evtType, const QString& stateKey, FnT&& fn) const + { + return lift(std::forward(fn), get(evtType, stateKey)); + } + + template + auto query(const QString& stateKey, FnT&& fn) const + { + using EventT = std::decay_t>; + static_assert(std::is_base_of_v); + return lift(std::forward(fn), get(stateKey)); + } + + template + auto queryOr(const QString& evtType, const QString& stateKey, FnT&& fn, + FallbackT&& fallback) const + { + return lift(std::forward(fn), get(evtType, stateKey)) + .value_or(std::forward(fallback)); + } + + template + auto query(FnT&& fn) const + { + return query({}, std::forward(fn)); + } + + template + auto queryOr(const QString& stateKey, FnT&& fn, FallbackT&& fallback) const + { + using EventT = std::decay_t>; + static_assert(std::is_base_of_v); + return lift(std::forward(fn), get(stateKey)) + .value_or(std::forward(fallback)); + } + + template + auto queryOr(FnT&& fn, FallbackT&& fallback) const + { + return queryOr({}, std::forward(fn), + std::forward(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(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(id(), move(evtC)); - // The state will be updated locally after it arrives with sync + if (const auto& maybeEvt = r->currentState().get(id())) { + auto content = maybeEvt->content(); + if (content.membership == Membership::Join) { + content.displayName = sanitized(newName); + r->setState(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 -- cgit v1.2.3 From a1fcad591968ec717214a73a2dbe78f608207bc5 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 5 Dec 2021 10:59:44 +0100 Subject: Move away Omittable static tests to autotests/ These are not required to build libQuotient, and omittable.cpp entirely consisted of them. --- CMakeLists.txt | 2 +- autotests/CMakeLists.txt | 1 + autotests/utiltests.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++++ lib/omittable.cpp | 34 ---------------------------------- 4 files changed, 47 insertions(+), 35 deletions(-) create mode 100644 autotests/utiltests.cpp delete mode 100644 lib/omittable.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ddf11680..89eb996a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,7 +127,7 @@ list(APPEND lib_SRCS lib/quotient_common.h lib/quotient_export.h lib/function_traits.h lib/function_traits.cpp - lib/omittable.h lib/omittable.cpp + lib/omittable.h lib/networkaccessmanager.h lib/networkaccessmanager.cpp lib/connectiondata.h lib/connectiondata.cpp lib/connection.h lib/connection.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 +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "omittable.h" + +#include + +// compile-time Omittable<> tests +using namespace Quotient; + +Omittable testFn(bool) { return 0; } +bool testFn2(int) { return false; } +static_assert( + std::is_same_v>().then(testFn)), + Omittable>); +static_assert( + std::is_same_v< + decltype(std::declval>().then_or(testFn, 0)), int>); +static_assert( + std::is_same_v>().then(testFn)), + Omittable>); +static_assert(std::is_same_v>() + .then(testFn2) + .then(testFn)), + Omittable>); +static_assert(std::is_same_v>() + .then(testFn) + .then_or(testFn2, false)), + bool>); + +constexpr auto visitTestFn(int, bool) { return false; } +static_assert( + std::is_same_v, decltype(lift(testFn2, Omittable()))>); +static_assert(std::is_same_v, + decltype(lift(visitTestFn, Omittable(), + Omittable()))>); + +class TestUtils : public QObject { + Q_OBJECT +private Q_SLOTS: + // TODO +}; + +QTEST_APPLESS_MAIN(TestUtils) +#include "utiltests.moc" diff --git a/lib/omittable.cpp b/lib/omittable.cpp deleted file mode 100644 index 245ae721..00000000 --- a/lib/omittable.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Kitsune Ral -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "omittable.h" - -// Omittable<> tests -using namespace Quotient; - -Omittable testFn(bool) { return 0; } -bool testFn2(int) { return false; } -static_assert( - std::is_same_v>().then(testFn)), - Omittable>); -static_assert( - std::is_same_v< - decltype(std::declval>().then_or(testFn, 0)), int>); -static_assert( - std::is_same_v>().then(testFn)), - Omittable>); -static_assert(std::is_same_v>() - .then(testFn2) - .then(testFn)), - Omittable>); -static_assert(std::is_same_v>() - .then(testFn) - .then_or(testFn2, false)), - bool>); - -constexpr auto visitTestFn(int, bool) { return false; } -static_assert( - std::is_same_v, decltype(lift(testFn2, Omittable()))>); -static_assert(std::is_same_v, - decltype(lift(visitTestFn, Omittable(), - Omittable()))>); -- cgit v1.2.3 From 9ae0d4e45befc79f621b03dc4efe869cd4277e06 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sun, 23 Jan 2022 10:32:40 +0100 Subject: Refactor Room::setState() There are two important aspects here: - Introducing Room::setState(evtType, stateKey, contentJson). These components are ultimately what is getting sent to the homeserver, so it makes sense to expose a respective `setState()` overload. Unlike setState(event) the new overload can be Q_INVOKABLE. - Room::setState() is no more const. Although it doesn't cause any changes in Room class (and only transient changes in Room::Private), it ultimately initiates a change in the room state, so calling it const has always been a bit of hypocrisy. If you relied on that, you most likely do something wrong (see the fix to User::rename() in this very commit for a simple example of such wrongness). Also: the backend is simplified by calling the original templated Room::setState() instead of calling Room::Private::requestSetState() that does exactly the same thing. --- lib/room.cpp | 36 ++++++++++++++++++++---------------- lib/room.h | 11 ++++++++--- lib/user.cpp | 2 +- lib/user.h | 2 +- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/lib/room.cpp b/lib/room.cpp index 1450eb3b..abd6110c 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -297,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); @@ -305,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( - id, event.matrixType(), event.stateKey(), event.contentJson()); - } - - template - auto requestSetState(ArgTs&&... args) - { - return requestSetState(EvT(std::forward(args)...)); + return connection->callApi(id, evtType, stateKey, + contentJson); } /*! Apply redaction to the timeline @@ -2126,33 +2122,41 @@ QString Room::postJson(const QString& matrixType, return d->sendEvent(loadEvent(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(newName); + setState(newName); } void Room::setCanonicalAlias(const QString& newAlias) { - d->requestSetState(newAlias, altAliases()); + setState(newAlias, altAliases()); } void Room::setPinnedEvents(const QStringList& events) { - d->requestSetState(events); + setState(events); } void Room::setLocalAliases(const QStringList& aliases) { - d->requestSetState(canonicalAlias(), aliases); + setState(canonicalAlias(), aliases); } void Room::setTopic(const QString& newTopic) { - d->requestSetState(newTopic); + setState(newTopic); } bool isEchoEvent(const RoomEventPtr& le, const PendingEventItem& re) diff --git a/lib/room.h b/lib/room.h index d49cfb55..9f70d77a 100644 --- a/lib/room.h +++ b/lib/room.h @@ -781,6 +781,9 @@ public: /// \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 @@ -790,7 +793,7 @@ public: //! the Matrix event type defined by \p EvT and the event content produced //! via EvT::contentJson(). template - auto setState(ArgTs&&... args) const + auto setState(ArgTs&&... args) { return setState(EvT(std::forward(args)...)); } @@ -824,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/user.cpp b/lib/user.cpp index f7840c40..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()" 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 -- cgit v1.2.3 From 08612cb253417fe70ef45a1ad08663a0745d748a Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 10 Dec 2021 19:26:23 +0100 Subject: No more default construction of events Default construction was only done to support stubbed state in Room and even that did not really use those, opting to construct an event from an empty QJsonObject instead. Now that Room doesn't have stubbed state, default constructors are even less needed. --- lib/events/encryptionevent.h | 20 ++++++++++++-------- lib/events/roomcreateevent.h | 1 - lib/events/roommemberevent.h | 10 +++------- lib/events/roompowerlevelsevent.h | 4 +++- lib/events/roomtombstoneevent.h | 1 - lib/events/simplestateevents.h | 1 - 6 files changed, 18 insertions(+), 19 deletions(-) diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h index dfb28b2f..56913393 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -12,7 +12,11 @@ class QUOTIENT_API EncryptionEventContent : public EventContent::Base { public: enum EncryptionType : size_t { MegolmV1AesSha2 = 0, Undefined }; - explicit EncryptionEventContent(EncryptionType et = Undefined); + explicit(false) EncryptionEventContent(EncryptionType et); + [[deprecated("This constructor will require explicit EncryptionType soon")]] // + explicit EncryptionEventContent() + : EncryptionEventContent(Undefined) + {} explicit EncryptionEventContent(const QJsonObject& json); EncryptionType encryption; @@ -34,15 +38,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 - EncryptionEvent(ArgTs&&... contentArgs) - : StateEvent(typeId(), matrixTypeId(), QString(), - std::forward(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/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/roommemberevent.h b/lib/events/roommemberevent.h index 5e446dbe..3296ae22 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) - {} + explicit(false) 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 - RoomMemberEvent(const QString& userId, ArgTs&&... contentArgs) - : StateEvent(typeId(), matrixTypeId(), userId, - std::forward(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 { - 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 \ explicit _Name(T&& value) \ : StateEvent(typeId(), matrixTypeId(), QString(), \ -- cgit v1.2.3 From 1747575321cda4fc11f90c2ffb2148a69d0d9946 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 28 Jan 2022 17:08:10 +0100 Subject: QUO_IMPLICIT Because Apple Clang choked on `explicit(false)`. --- lib/events/encryptionevent.h | 3 ++- lib/events/roommemberevent.h | 2 +- lib/quotient_common.h | 7 +++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h index 56913393..124ced33 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -6,13 +6,14 @@ #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(false) EncryptionEventContent(EncryptionType et); + QUO_IMPLICIT EncryptionEventContent(EncryptionType et); [[deprecated("This constructor will require explicit EncryptionType soon")]] // explicit EncryptionEventContent() : EncryptionEventContent(Undefined) diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h index 3296ae22..ceb7826b 100644 --- a/lib/events/roommemberevent.h +++ b/lib/events/roommemberevent.h @@ -15,7 +15,7 @@ public: using MembershipType [[deprecated("Use Quotient::Membership instead")]] = Membership; - explicit(false) MemberEventContent(Membership ms) : membership(ms) {} + QUO_IMPLICIT MemberEventContent(Membership ms) : membership(ms) {} explicit MemberEventContent(const QJsonObject& json); Membership membership; 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 -- cgit v1.2.3 From 350f40d07943a32319182bac6a21adf456642075 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Sat, 29 Jan 2022 16:12:22 +0100 Subject: SyncData: expect self-contained /sync response SyncData can load room objects out-of-line. This is only expected when loading data from the cache (and since quite long ago, the cache always saves room objects out of line, avoiding too large JSON payloads that Qt parser chokes on). However, the code processed /sync response in the same way; in particular, this meant that SyncData filled the vector of unresolved room ids even when it came from /sync. SyncJob then looked at this vector and entered an error state if it was not empty. Well, payloads from the wire can be weird and it ultimately came to pass that a homeserver returned a non-object against a given room key, triggering the unresolved rooms branch in SyncJob - and stalling the whole sync loop as a result (https://invent.kde.org/network/neochat/-/issues/500). With this commit SyncData only fills unresolvedRoomIds when loading rooms from the cache (with the implied fallback of discarding the cache and loading from /sync anew instead). Respectively, SyncJob must never end up with SyncData that has unresolved rooms (even if those occur in the actual payload like in the mentioned issue, those rooms will be completely empty instead); the added assertion only guards for internal consistency. --- lib/jobs/syncjob.cpp | 6 ++++-- lib/syncdata.cpp | 20 ++++++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/jobs/syncjob.cpp b/lib/jobs/syncjob.cpp index 9b1b46f0..f5c632bf 100644 --- a/lib/jobs/syncjob.cpp +++ b/lib/jobs/syncjob.cpp @@ -37,10 +37,12 @@ SyncJob::SyncJob(const QString& since, const Filter& filter, int timeout, BaseJob::Status SyncJob::prepareResult() { d.parseJson(jsonData()); - if (d.unresolvedRooms().isEmpty()) + if (Q_LIKELY(d.unresolvedRooms().isEmpty())) return Success; - qCCritical(MAIN).noquote() << "Incomplete sync response, missing rooms:" + Q_ASSERT(d.unresolvedRooms().isEmpty()); + qCCritical(MAIN).noquote() << "Rooms missing after processing sync " + "response, possibly a bug in SyncData: " << d.unresolvedRooms().join(','); return IncorrectResponse; } diff --git a/lib/syncdata.cpp b/lib/syncdata.cpp index 396e77eb..b0cd8e4d 100644 --- a/lib/syncdata.cpp +++ b/lib/syncdata.cpp @@ -185,14 +185,18 @@ void SyncData::parseJson(const QJsonObject& json, const QString& baseDir) // We have a Qt container on the right and an STL one on the left roomData.reserve(roomData.size() + static_cast(rs.size())); for (auto roomIt = rs.begin(); roomIt != rs.end(); ++roomIt) { - auto roomJson = - roomIt->isObject() - ? roomIt->toObject() - : loadJson(baseDir + fileNameForRoom(roomIt.key())); - if (roomJson.isEmpty()) { - unresolvedRoomIds.push_back(roomIt.key()); - continue; - } + QJsonObject roomJson; + if (!baseDir.isEmpty()) { + // Loading data from the local cache, with room objects saved in + // individual files rather than inline + roomJson = loadJson(baseDir + fileNameForRoom(roomIt.key())); + if (roomJson.isEmpty()) { + unresolvedRoomIds.push_back(roomIt.key()); + continue; + } + } else // When loading from /sync response, everything is inline + roomJson = roomIt->toObject(); + roomData.emplace_back(roomIt.key(), joinState, roomJson); const auto& r = roomData.back(); totalEvents += r.state.size() + r.ephemeral.size() -- cgit v1.2.3 From 73623b69477352f901aa0d02a667bd2438a91491 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 11 Feb 2022 14:22:57 +0100 Subject: EventRelation: defer to non-deprecated symbols --- lib/events/roommessageevent.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/events/roommessageevent.h b/lib/events/roommessageevent.h index 44ef05fb..03a51328 100644 --- a/lib/events/roommessageevent.h +++ b/lib/events/roommessageevent.h @@ -101,8 +101,8 @@ namespace EventContent { struct [[deprecated("Use Quotient::EventRelation instead")]] RelatesTo : EventRelation { - static constexpr auto ReplyTypeId() { return Reply(); } - static constexpr auto ReplacementTypeId() { return Replacement(); } + static constexpr auto ReplyTypeId() { return ReplyType; } + static constexpr auto ReplacementTypeId() { return ReplacementType; } }; [[deprecated("Use EventRelation::replyTo() instead")]] inline auto replyTo(QString eventId) -- cgit v1.2.3 From 4f581f22d707fd226a56400817c9e47e54116589 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 11 Feb 2022 14:21:25 +0100 Subject: Better documentation for QUO_DECLARE_FLAGS[_NS] --- lib/quotient_common.h | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/lib/quotient_common.h b/lib/quotient_common.h index b3fb3efa..2b785a39 100644 --- a/lib/quotient_common.h +++ b/lib/quotient_common.h @@ -9,17 +9,33 @@ #include -// See https://bugreports.qt.io/browse/QTBUG-82295 - despite the comment that -// Q_FLAG[_NS] "should" be applied to the enum only, Qt doesn't allow to wrap -// a flag type into a QVariant then. The macros below define Q_FLAG[_NS] and on -// top of that add Q_ENUM[_NS]_IMPL which is a part of Q_ENUM() macro that -// enables the metatype data but goes under the moc radar to avoid double -// registration of the same data in the map defined in moc_*.cpp + +//! \brief Quotient replacement for the Q_FLAG/Q_DECLARE_FLAGS combination +//! +//! Although the comment in QTBUG-82295 says that Q_FLAG[_NS] "should" be +//! applied to the enum type only, Qt then doesn't allow to wrap the +//! corresponding flag type (defined with Q_DECLARE_FLAGS) into a QVariant. +//! This macro defines Q_FLAG and on top of that adds Q_ENUM_IMPL which is +//! a part of Q_ENUM() macro that enables the metatype data but goes under +//! the moc radar to avoid double registration of the same data in the map +//! defined in moc_*.cpp. +//! +//! Simply put, instead of using Q_FLAG/Q_DECLARE_FLAGS combo (and struggling +//! to figure out what you should pass to Q_FLAG if you want to make it +//! wrappable in a QVariant) use the macro below, and things will just work. +//! +//! \sa https://bugreports.qt.io/browse/QTBUG-82295 #define QUO_DECLARE_FLAGS(Flags, Enum) \ Q_DECLARE_FLAGS(Flags, Enum) \ Q_ENUM_IMPL(Enum) \ Q_FLAG(Flags) +//! \brief Quotient replacement for the Q_FLAG_NS/Q_DECLARE_FLAGS combination +//! +//! This is the equivalent of QUO_DECLARE_FLAGS for enums declared at the +//! namespace level (be sure to provide Q_NAMESPACE _in the same file_ +//! as the enum definition and this macro). +//! \sa QUO_DECLARE_FLAGS #define QUO_DECLARE_FLAGS_NS(Flags, Enum) \ Q_DECLARE_FLAGS(Flags, Enum) \ Q_ENUM_NS_IMPL(Enum) \ -- cgit v1.2.3 From 10ac7c13cdcd62b62af6e89cb726376cfbc53302 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Mon, 3 Jan 2022 21:59:04 +0100 Subject: Enable to/fromJson to work with non-assignable objects Previously you could not use toJson() on a polymorphic structure such as one of those defined in eventcontent.h because it is not assignable and the default specialisation of JsonObjectConverter used assignment. To avoid that limitation, one had to specialise JsonObjectConverter for each descendant of EventContent::Base, which is a lot of boilerplate. The new JsonConverter (the template underpinning the "default" to/fromJson implementation) improves on two things: 1. dump() allows to construct your own QJsonObject - or anything else convertable to QJsonValue - instead of requiring to fill in the pre-constructed one. 2. load() allows to construct your value type directly from QJsonObject instead of default-constructing it in advance. --- lib/converters.h | 51 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/lib/converters.h b/lib/converters.h index 8ddf6e45..515c96fd 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -14,6 +14,7 @@ #include #include +#include #include class QVariant; @@ -21,10 +22,29 @@ class QVariant; namespace Quotient { template struct JsonObjectConverter { - static void dumpTo(QJsonObject& jo, const T& pod) { jo = pod.toJson(); } - static void fillFrom(const QJsonObject& jo, T& pod) { pod = T(jo); } + // To be implemented in specialisations + static void dumpTo(QJsonObject&, const T&) = delete; + static void fillFrom(const QJsonObject&, T&) = delete; }; +namespace _impl { + template + struct JsonExporter { + static QJsonObject dump(const T& data) + { + QJsonObject jo; + JsonObjectConverter::dumpTo(jo, data); + return jo; + } + }; + + template + struct JsonExporter< + T, std::enable_if_t>> { + static auto dump(const T& data) { return data.toJson(); } + }; +} + //! \brief The switchboard for extra conversion algorithms behind from/toJson //! //! This template is mainly intended for partial conversion specialisations @@ -41,18 +61,25 @@ struct JsonObjectConverter { //! that they are not supported and it's not feasible to support those by means //! of overloading toJson() and specialising fromJson(). template -struct JsonConverter { - static QJsonObject dump(const T& pod) - { - QJsonObject jo; - JsonObjectConverter::dumpTo(jo, pod); - return jo; - } +struct JsonConverter : _impl::JsonExporter { + // Unfortunately, if constexpr doesn't work with dump() and T::toJson + // because trying to check invocability of T::toJson hits a hard + // (non-SFINAE) compilation error if the member is not there. Hence a bit + // more verbose SFINAE construct in _impl::JsonExporter. + static T doLoad(const QJsonObject& jo) { - T pod; - JsonObjectConverter::fillFrom(jo, pod); - return pod; + // 'else' below are required to suppress code generation for unused + // branches - 'return' is not enough + if constexpr (std::is_same_v) + return jo; + else if constexpr (std::is_constructible_v) + return T(jo); + else { + T pod; + JsonObjectConverter::fillFrom(jo, pod); + return pod; + } } static T load(const QJsonValue& jv) { return doLoad(jv.toObject()); } static T load(const QJsonDocument& jd) { return doLoad(jd.object()); } -- cgit v1.2.3