Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better power level checking, part 1 #799

Merged
merged 5 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 21 additions & 30 deletions Quotient/events/event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,55 +4,44 @@
#include "event.h"

#include "../logging_categories_p.h"
#include "stateevent.h"
#include "../ranges_extras.h"

#include <QtCore/QJsonDocument>

#if Quotient_VERSION_MAJOR == 0 && Quotient_VERSION_MINOR <= 9
#include "stateevent.h" // For deprecated isStateEvent(); remove, once Event::isStateEvent() is gone
#endif

using namespace Quotient;

void AbstractEventMetaType::addDerived(const AbstractEventMetaType* newType)
{
if (const auto existing =
std::find_if(derivedTypes.cbegin(), derivedTypes.cend(),
[&newType](const AbstractEventMetaType* t) {
return t->matrixId == newType->matrixId;
});
existing != derivedTypes.cend())
{
findIndirect(_derivedTypes, newType->matrixId, &AbstractEventMetaType::matrixId);
existing != _derivedTypes.cend()) {
if (*existing == newType)
return;
// Two different metatype objects claim the same Matrix type id; this
// is not normal, so give as much information as possible to diagnose
if ((*existing)->className == newType->className) {
qCritical(EVENTS)
<< newType->className << "claims" << newType->matrixId
<< "repeatedly; check that it's exported across translation "
"units or shared objects";
qCritical(EVENTS) << newType->className << "claims" << newType->matrixId
<< "repeatedly; check that it's exported across translation "
"units or shared objects";
Q_ASSERT(false); // That situation is plain wrong
return; // So maybe std::terminate() even?
}
qWarning(EVENTS).nospace()
<< newType->matrixId << " is already mapped to "
<< (*existing)->className << " before " << newType->className
<< "; unless the two have different isValid() conditions, the "
"latter class will never be used";
qWarning(EVENTS).nospace() << newType->matrixId << " is already mapped to "
<< (*existing)->className << " before " << newType->className
<< "; unless the two have different isValid() conditions, the "
"latter class will never be used";
}
derivedTypes.emplace_back(newType);
qDebug(EVENTS).nospace()
<< newType->matrixId << " -> " << newType->className << "; "
<< derivedTypes.size() << " derived type(s) registered for "
<< className;
_derivedTypes.emplace_back(newType);
qDebug(EVENTS).nospace() << newType->matrixId << " -> " << newType->className << "; "
<< _derivedTypes.size() << " derived type(s) registered for "
<< className;
}

Event::Event(const QJsonObject& json)
: _json(json)
{
if (!json.contains(ContentKey)
&& !json.value(UnsignedKey).toObject().contains(RedactedCauseKey)) {
qCWarning(EVENTS) << "Event without 'content' node";
qCWarning(EVENTS) << formatJson << json;
}
}
Event::Event(const QJsonObject& json) : _json(json) {}

Event::~Event() = default;

Expand All @@ -68,7 +57,9 @@ const QJsonObject Event::unsignedJson() const
return fullJson()[UnsignedKey].toObject();
}

#if Quotient_VERSION_MAJOR == 0 && Quotient_VERSION_MINOR <= 9
bool Event::isStateEvent() const { return is<StateEvent>(); }
#endif

void Event::dumpTo(QDebug dbg) const
{
Expand Down
13 changes: 7 additions & 6 deletions Quotient/events/event.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ constexpr inline auto TypeKey = "type"_L1;
constexpr inline auto BodyKey = "body"_L1;
constexpr inline auto ContentKey = "content"_L1;
constexpr inline auto SenderKey = "sender"_L1;
constexpr inline auto UnsignedKey = "unsigned"_L1;

using event_type_t = QLatin1String;

Expand Down Expand Up @@ -55,6 +56,7 @@ class QUOTIENT_API AbstractEventMetaType {
}

void addDerived(const AbstractEventMetaType* newType);
auto derivedTypes() const { return std::span(_derivedTypes); }

virtual ~AbstractEventMetaType() = default;

Expand All @@ -69,7 +71,7 @@ class QUOTIENT_API AbstractEventMetaType {
Event*& event) const = 0;

private:
std::vector<const AbstractEventMetaType*> derivedTypes{};
std::vector<const AbstractEventMetaType*> _derivedTypes{};
Q_DISABLE_COPY_MOVE(AbstractEventMetaType)
};

Expand Down Expand Up @@ -146,7 +148,7 @@ class QUOTIENT_API EventMetaType : public AbstractEventMetaType {
if (EventT::TypeId != type)
return false;
} else {
for (const auto& p : derivedTypes) {
for (const auto& p : _derivedTypes) {
p->doLoadFrom(fullJson, type, event);
if (event) {
Q_ASSERT(is<EventT>(*event));
Expand Down Expand Up @@ -328,10 +330,9 @@ class QUOTIENT_API Event {
return dbg;
}

// State events are quite special in Matrix; so isStateEvent() is here,
// as an exception. For other base events, Event::is<>() and
// Quotient::is<>() should be used; don't add is* methods here
bool isStateEvent() const;
#if Quotient_VERSION_MAJOR == 0 && Quotient_VERSION_MINOR <= 9
[[deprecated("isStateEvent() has moved to RoomEvent")]] bool isStateEvent() const;
#endif

protected:
friend class EventMetaType<Event>; // To access the below constructor
Expand Down
21 changes: 19 additions & 2 deletions Quotient/events/roomevent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@

#include "roomevent.h"

#include "../logging_categories_p.h"
#include "encryptedevent.h"
#include "redactionevent.h"
#include "stateevent.h"

#include "encryptedevent.h"
#include "../logging_categories_p.h"

using namespace Quotient;

Expand Down Expand Up @@ -48,6 +49,8 @@ QString RoomEvent::transactionId() const
return unsignedPart<QString>("transaction_id"_L1);
}

bool RoomEvent::isStateEvent() const { return is<StateEvent>(); }

QString RoomEvent::stateKey() const
{
return fullJson()[StateKeyKey].toString();
Expand Down Expand Up @@ -98,3 +101,17 @@ const QJsonObject RoomEvent::encryptedJson() const
}
return _originalEvent->fullJson();
}

namespace {
bool containsEventType(const auto& haystack, const auto& needle)
{
return std::ranges::any_of(haystack, [needle](const AbstractEventMetaType* candidate) {
return candidate->matrixId == needle || containsEventType(candidate->derivedTypes(), needle);
});
}
}

bool Quotient::isStateEvent(const QString& eventTypeId)
{
return containsEventType(StateEvent::BaseMetaType.derivedTypes(), eventTypeId);
}
10 changes: 9 additions & 1 deletion Quotient/events/roomevent.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ constexpr inline auto RoomIdKey = "room_id"_L1;
constexpr inline auto StateKeyKey = "state_key"_L1;
constexpr inline auto RedactedCauseKey = "redacted_because"_L1;
constexpr inline auto RelatesToKey = "m.relates_to"_L1;
constexpr inline auto UnsignedKey = "unsigned"_L1;

class RedactionEvent;
class EncryptedEvent;
Expand Down Expand Up @@ -49,6 +48,12 @@ class QUOTIENT_API RoomEvent : public Event {
//! The transaction_id JSON value for the event.
QString transactionId() const;

// State events are special in Matrix; so isStateEvent() and stateKey() are here,
// as an exception. For other event types (including base types), Event::is<>() and
// Quotient::is<>() should be used

bool isStateEvent() const;

QString stateKey() const;

//! \brief Fill the pending event object with the room id
Expand Down Expand Up @@ -89,6 +94,9 @@ using RoomEventPtr = event_ptr_tt<RoomEvent>;
using RoomEvents = EventsArray<RoomEvent>;
using RoomEventsRange = std::ranges::subrange<RoomEvents::iterator>;

//! \brief Determine whether a given event type is that of a state event
QUOTIENT_API bool isStateEvent(const QString& eventTypeId);

} // namespace Quotient
Q_DECLARE_METATYPE(Quotient::RoomEvent*)
Q_DECLARE_METATYPE(const Quotient::RoomEvent*)
55 changes: 29 additions & 26 deletions Quotient/events/roompowerlevelsevent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,36 @@

using namespace Quotient;

// The default values used below are defined in
// https://spec.matrix.org/v1.3/client-server-api/#mroompower_levels
PowerLevelsEventContent::PowerLevelsEventContent(const QJsonObject& json) :
invite(json["invite"_L1].toInt(50)),
kick(json["kick"_L1].toInt(50)),
ban(json["ban"_L1].toInt(50)),
redact(json["redact"_L1].toInt(50)),
events(fromJson<QHash<QString, int>>(json["events"_L1])),
eventsDefault(json["events_default"_L1].toInt(0)),
stateDefault(json["state_default"_L1].toInt(50)),
users(fromJson<QHash<QString, int>>(json["users"_L1])),
usersDefault(json["users_default"_L1].toInt(0)),
notifications(Notifications{json["notifications"_L1].toObject()["room"_L1].toInt(50)})
{}

QJsonObject PowerLevelsEventContent::toJson() const
PowerLevelsEventContent JsonConverter<PowerLevelsEventContent>::load(const QJsonValue& jv)
{
return QJsonObject{ { u"invite"_s, invite },
{ u"kick"_s, kick },
{ u"ban"_s, ban },
{ u"redact"_s, redact },
{ u"events"_s, Quotient::toJson(events) },
{ u"events_default"_s, eventsDefault },
{ u"state_default"_s, stateDefault },
{ u"users"_s, Quotient::toJson(users) },
{ u"users_default"_s, usersDefault },
{ u"notifications"_s, QJsonObject{ { u"room"_s, notifications.room } } } };
const auto& jo = jv.toObject();
PowerLevelsEventContent c;
#define FROM_JSON(member) fromJson(jo[toSnakeCase(#member##_L1)], c.member)
FROM_JSON(invite);
FROM_JSON(kick);
FROM_JSON(ban);
FROM_JSON(redact);
FROM_JSON(events);
FROM_JSON(eventsDefault);
FROM_JSON(stateDefault);
FROM_JSON(users);
FROM_JSON(usersDefault);
fromJson(jo["notifications"_L1]["room"_L1], c.notifications.room);
#undef FROM_JSON
return c;
}

QJsonObject JsonConverter<PowerLevelsEventContent>::dump(const PowerLevelsEventContent& c) {
return QJsonObject{ { u"invite"_s, c.invite },
{ u"kick"_s, c.kick },
{ u"ban"_s, c.ban },
{ u"redact"_s, c.redact },
{ u"events"_s, Quotient::toJson(c.events) },
{ u"events_default"_s, c.eventsDefault },
{ u"state_default"_s, c.stateDefault },
{ u"users"_s, Quotient::toJson(c.users) },
{ u"users_default"_s, c.usersDefault },
{ u"notifications"_s, QJsonObject{ { u"room"_s, c.notifications.room } } } };
}

int RoomPowerLevelsEvent::powerLevelForEvent(const QString& eventTypeId) const
Expand Down
64 changes: 49 additions & 15 deletions Quotient/events/roompowerlevelsevent.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,33 @@
#include "stateevent.h"

namespace Quotient {

struct QUOTIENT_API PowerLevelsEventContent {
struct Notifications {
int room;
};
// See https://spec.matrix.org/v1.11/client-server-api/#mroompower_levels for the defaults

explicit PowerLevelsEventContent(const QJsonObject& json);
QJsonObject toJson() const;
int invite = 0;
int kick = 50;
int ban = 50;

int invite;
int kick;
int ban;
int redact = 50;

int redact;
QHash<QString, int> events{};
int eventsDefault = 0;
int stateDefault = 50;

QHash<QString, int> events;
int eventsDefault;
int stateDefault;
QHash<QString, int> users{};
int usersDefault = 0;

QHash<QString, int> users;
int usersDefault;
struct Notifications {
int room = 50;
};
Notifications notifications{};
};

Notifications notifications;
template <>
struct QUOTIENT_API JsonConverter<PowerLevelsEventContent> {
static PowerLevelsEventContent load(const QJsonValue& jv);
static QJsonObject dump(const PowerLevelsEventContent& c);
};

class QUOTIENT_API RoomPowerLevelsEvent
Expand All @@ -52,8 +57,37 @@ class QUOTIENT_API RoomPowerLevelsEvent

int roomNotification() const { return content().notifications.room; }

//! \brief Get the power level for message events of a given type
//!
//! \note You normally should not compare power levels returned from this
//! and other powerLevelFor*() functions directly; use
//! Room::canSendEvents() instead
int powerLevelForEvent(const QString& eventTypeId) const;

//! \brief Get the power level for state events of a given type
//!
//! \note You normally should not compare power levels returned from this
//! and other powerLevelFor*() functions directly; use
//! Room::canSetState() instead
int powerLevelForState(const QString& eventTypeId) const;

//! \brief Get the power level for a given user
//!
//! \note You normally should not directly use power levels returned by this
//! and other powerLevelFor*() functions; use Room API instead
//! \sa Room::canSend, Room::canSendEvents, Room::canSetState,
//! Room::effectivePowerLevel
int powerLevelForUser(const QString& userId) const;

template <EventClass EvT>
int powerLevelForEventType() const
{
if constexpr (std::is_base_of_v<StateEvent, EvT>) {
return powerLevelForState(EvT::TypeId);
} else {
return powerLevelForEvent(EvT::TypeId);
}
}
};

} // namespace Quotient
Loading
Loading