diff options
author | Nicolas Werner <nicolas.werner@hotmail.de> | 2022-09-28 02:09:04 +0200 |
---|---|---|
committer | Nicolas Werner <nicolas.werner@hotmail.de> | 2022-09-28 02:09:04 +0200 |
commit | 051c25d5b87c2351df46173f19b907cea436fa3b (patch) | |
tree | 7e68ff95c1a678cc614f908e67cef5fe0c6a624c /src | |
parent | Fix infinite loop that can be triggered by some invalid html (diff) | |
download | nheko-051c25d5b87c2351df46173f19b907cea436fa3b.tar.xz |
Allow editing permissions in spaces recursively
Diffstat (limited to 'src')
-rw-r--r-- | src/PowerlevelsEditModels.cpp | 239 | ||||
-rw-r--r-- | src/PowerlevelsEditModels.h | 95 |
2 files changed, 310 insertions, 24 deletions
diff --git a/src/PowerlevelsEditModels.cpp b/src/PowerlevelsEditModels.cpp index fcfde26e..09e5b05d 100644 --- a/src/PowerlevelsEditModels.cpp +++ b/src/PowerlevelsEditModels.cpp @@ -4,8 +4,12 @@ #include "PowerlevelsEditModels.h" +#include <QCoreApplication> +#include <QTimer> + #include <algorithm> #include <set> +#include <unordered_set> #include "Cache.h" #include "Cache_p.h" @@ -76,7 +80,7 @@ PowerlevelsTypeListModel::PowerlevelsTypeListModel(const std::string &rid, } std::map<std::string, mtx::events::state::power_level_t, std::less<>> -PowerlevelsTypeListModel::toEvents() +PowerlevelsTypeListModel::toEvents() const { std::map<std::string, mtx::events::state::power_level_t, std::less<>> m; for (const auto &[key, pl] : qAsConst(types)) @@ -85,7 +89,7 @@ PowerlevelsTypeListModel::toEvents() return m; } mtx::events::state::power_level_t -PowerlevelsTypeListModel::kick() +PowerlevelsTypeListModel::kick() const { for (const auto &[key, pl] : qAsConst(types)) if (key == "kick") @@ -93,7 +97,7 @@ PowerlevelsTypeListModel::kick() return powerLevels_.users_default; } mtx::events::state::power_level_t -PowerlevelsTypeListModel::invite() +PowerlevelsTypeListModel::invite() const { for (const auto &[key, pl] : qAsConst(types)) if (key == "invite") @@ -101,7 +105,7 @@ PowerlevelsTypeListModel::invite() return powerLevels_.users_default; } mtx::events::state::power_level_t -PowerlevelsTypeListModel::ban() +PowerlevelsTypeListModel::ban() const { for (const auto &[key, pl] : qAsConst(types)) if (key == "ban") @@ -109,7 +113,7 @@ PowerlevelsTypeListModel::ban() return powerLevels_.users_default; } mtx::events::state::power_level_t -PowerlevelsTypeListModel::eventsDefault() +PowerlevelsTypeListModel::eventsDefault() const { for (const auto &[key, pl] : qAsConst(types)) if (key == "zdefault_events") @@ -117,7 +121,7 @@ PowerlevelsTypeListModel::eventsDefault() return powerLevels_.users_default; } mtx::events::state::power_level_t -PowerlevelsTypeListModel::stateDefault() +PowerlevelsTypeListModel::stateDefault() const { for (const auto &[key, pl] : qAsConst(types)) if (key == "zdefault_states") @@ -390,7 +394,7 @@ PowerlevelsUserListModel::PowerlevelsUserListModel(const std::string &rid, } std::map<std::string, mtx::events::state::power_level_t, std::less<>> -PowerlevelsUserListModel::toUsers() +PowerlevelsUserListModel::toUsers() const { std::map<std::string, mtx::events::state::power_level_t, std::less<>> m; for (const auto &[key, pl] : qAsConst(users)) @@ -399,7 +403,7 @@ PowerlevelsUserListModel::toUsers() return m; } mtx::events::state::power_level_t -PowerlevelsUserListModel::usersDefault() +PowerlevelsUserListModel::usersDefault() const { for (const auto &[key, pl] : qAsConst(users)) if (key == "default") @@ -565,6 +569,7 @@ PowerlevelEditingModels::PowerlevelEditingModels(QString room_id, QObject *paren .content) , types_(room_id.toStdString(), powerLevels_, this) , users_(room_id.toStdString(), powerLevels_, this) + , spaces_(room_id.toStdString(), powerLevels_, this) , room_id_(room_id.toStdString()) { connect(&types_, @@ -581,17 +586,31 @@ PowerlevelEditingModels::PowerlevelEditingModels(QString room_id, QObject *paren &PowerlevelEditingModels::defaultUserLevelChanged); } +bool +PowerlevelEditingModels::isSpace() const +{ + return cache::singleRoomInfo(room_id_).is_space; +} + +mtx::events::state::PowerLevels +PowerlevelEditingModels::calculateNewPowerlevel() const +{ + auto newPl = powerLevels_; + newPl.events = types_.toEvents(); + newPl.kick = types_.kick(); + newPl.invite = types_.invite(); + newPl.ban = types_.ban(); + newPl.events_default = types_.eventsDefault(); + newPl.state_default = types_.stateDefault(); + newPl.users = users_.toUsers(); + newPl.users_default = users_.usersDefault(); + return newPl; +} + void PowerlevelEditingModels::commit() { - powerLevels_.events = types_.toEvents(); - powerLevels_.kick = types_.kick(); - powerLevels_.invite = types_.invite(); - powerLevels_.ban = types_.ban(); - powerLevels_.events_default = types_.eventsDefault(); - powerLevels_.state_default = types_.stateDefault(); - powerLevels_.users = users_.toUsers(); - powerLevels_.users_default = users_.usersDefault(); + powerLevels_ = calculateNewPowerlevel(); http::client()->send_state_event( room_id_, powerLevels_, [](const mtx::responses::EventId &, mtx::http::RequestErr e) { @@ -605,6 +624,13 @@ PowerlevelEditingModels::commit() } void +PowerlevelEditingModels::updateSpacesModel() +{ + powerLevels_ = calculateNewPowerlevel(); + spaces_.newPowerlevels_ = powerLevels_; +} + +void PowerlevelEditingModels::addRole(int pl) { for (const auto &e : qAsConst(types_.types)) @@ -614,3 +640,184 @@ PowerlevelEditingModels::addRole(int pl) types_.addRole(pl); users_.addRole(pl); } + +static bool +samePl(const mtx::events::state::PowerLevels &a, const mtx::events::state::PowerLevels &b) +{ + return std::tie(a.events, + a.users_default, + a.users, + a.state_default, + a.users_default, + a.events_default, + a.ban, + a.kick, + a.invite, + a.notifications, + a.redact) == std::tie(b.events, + b.users_default, + b.users, + b.state_default, + b.users_default, + b.events_default, + b.ban, + b.kick, + b.invite, + b.notifications, + b.redact); +} + +PowerlevelsSpacesListModel::PowerlevelsSpacesListModel(const std::string &room_id_, + const mtx::events::state::PowerLevels &pl, + QObject *parent) + : QAbstractListModel(parent) + , room_id(std::move(room_id_)) + , oldPowerLevels_(std::move(pl)) +{ + beginResetModel(); + + spaces.push_back(Entry{room_id, oldPowerLevels_, true}); + + std::unordered_set<std::string> visited; + + std::function<void(const std::string &)> addChildren; + addChildren = [this, &addChildren, &visited](const std::string &space) { + if (visited.count(space)) + return; + else + visited.insert(space); + + for (const auto &s : cache::client()->getChildRoomIds(space)) { + auto parent = + cache::client()->getStateEvent<mtx::events::state::space::Parent>(s, space); + if (parent && parent->content.via && !parent->content.via->empty() && + parent->content.canonical) { + auto parent = cache::client()->getStateEvent<mtx::events::state::PowerLevels>(s); + + spaces.push_back( + Entry{s, parent ? parent->content : mtx::events::state::PowerLevels{}, false}); + addChildren(s); + } + } + }; + + addChildren(room_id); + + endResetModel(); + + updateToDefaults(); +} + +struct PowerLevelApplier +{ + std::vector<std::string> spaces; + mtx::events::state::PowerLevels pl; + + void next() + { + if (spaces.empty()) + return; + + auto room_id_ = spaces.back(); + http::client()->send_state_event( + room_id_, + pl, + [self = *this](const mtx::responses::EventId &, mtx::http::RequestErr e) mutable { + if (e) { + if (e->status_code == 429 && e->matrix_error.retry_after.count() != 0) { + QTimer::singleShot(e->matrix_error.retry_after, + [self = std::move(self)]() mutable { self.next(); }); + return; + } + + nhlog::net()->error("Failed to send PL event: {}", *e); + ChatPage::instance()->showNotification( + QCoreApplication::translate("PowerLevels", "Failed to update powerlevel: %1") + .arg(QString::fromStdString(e->matrix_error.error))); + } + self.spaces.pop_back(); + self.next(); + }); + } +}; + +void +PowerlevelsSpacesListModel::commit() +{ + std::vector<std::string> spacesToApplyTo; + + for (const auto &s : spaces) + if (s.apply) + spacesToApplyTo.push_back(s.roomid); + + PowerLevelApplier context{std::move(spacesToApplyTo), newPowerlevels_}; + context.next(); +} + +void +PowerlevelsSpacesListModel::updateToDefaults() +{ + for (int i = 1; i < spaces.size(); i++) { + spaces[i].apply = + applyToChildren_ && data(index(i), Roles::IsEditable).toBool() && + !data(index(i), Roles::IsAlreadyUpToDate).toBool() && + (overwriteDiverged_ || !data(index(i), Roles::IsDifferentFromBase).toBool()); + } + + if (spaces.size() > 1) + emit dataChanged(index(1), index(spaces.size() - 1), {Roles::ApplyPermissions}); +} + +bool +PowerlevelsSpacesListModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (role != Roles::ApplyPermissions || index.row() < 0 || index.row() >= spaces.size()) + return false; + + spaces[index.row()].apply = value.toBool(); + return true; +} + +QVariant +PowerlevelsSpacesListModel::data(QModelIndex const &index, int role) const +{ + auto row = index.row(); + if (row >= spaces.size() && row < 0) + return {}; + + if (role == Roles::DisplayName || role == Roles::AvatarUrl || role == Roles::IsSpace) { + auto info = cache::singleRoomInfo(spaces.at(row).roomid); + if (role == Roles::DisplayName) + return QString::fromStdString(info.name); + else if (role == Roles::AvatarUrl) + return QString::fromStdString(info.avatar_url); + else + return info.is_space; + } + + auto entry = spaces.at(row); + switch (role) { + case Roles::IsEditable: + return entry.pl.user_level(http::client()->user_id().to_string()) >= + entry.pl.state_level(to_string(mtx::events::EventType::RoomPowerLevels)); + case Roles::IsDifferentFromBase: + return !samePl(entry.pl, oldPowerLevels_); + case Roles::IsAlreadyUpToDate: + return samePl(entry.pl, newPowerlevels_); + case Roles::ApplyPermissions: + return entry.apply; + } + return {}; +} +QHash<int, QByteArray> +PowerlevelsSpacesListModel::roleNames() const +{ + return { + {DisplayName, "displayName"}, + {AvatarUrl, "avatarUrl"}, + {IsEditable, "isEditable"}, + {IsDifferentFromBase, "isDifferentFromBase"}, + {IsAlreadyUpToDate, "isAlreadyUpToDate"}, + {ApplyPermissions, "applyPermissions"}, + }; +} diff --git a/src/PowerlevelsEditModels.h b/src/PowerlevelsEditModels.h index 9aa955d2..515fdb56 100644 --- a/src/PowerlevelsEditModels.h +++ b/src/PowerlevelsEditModels.h @@ -48,12 +48,12 @@ public: const QModelIndex &destinationParent, int destinationChild) override; - std::map<std::string, mtx::events::state::power_level_t, std::less<>> toEvents(); - mtx::events::state::power_level_t kick(); - mtx::events::state::power_level_t invite(); - mtx::events::state::power_level_t ban(); - mtx::events::state::power_level_t eventsDefault(); - mtx::events::state::power_level_t stateDefault(); + std::map<std::string, mtx::events::state::power_level_t, std::less<>> toEvents() const; + mtx::events::state::power_level_t kick() const; + mtx::events::state::power_level_t invite() const; + mtx::events::state::power_level_t ban() const; + mtx::events::state::power_level_t eventsDefault() const; + mtx::events::state::power_level_t stateDefault() const; struct Entry { @@ -106,8 +106,8 @@ public: const QModelIndex &destinationParent, int destinationChild) override; - std::map<std::string, mtx::events::state::power_level_t, std::less<>> toUsers(); - mtx::events::state::power_level_t usersDefault(); + std::map<std::string, mtx::events::state::power_level_t, std::less<>> toUsers() const; + mtx::events::state::power_level_t usersDefault() const; struct Entry { @@ -122,38 +122,117 @@ public: mtx::events::state::PowerLevels powerLevels_; }; +class PowerlevelsSpacesListModel : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(bool applyToChildren READ applyToChildren WRITE setApplyToChildren NOTIFY + applyToChildrenChanged) + Q_PROPERTY(bool overwriteDiverged READ overwriteDiverged WRITE setOverwriteDiverged NOTIFY + overwriteDivergedChanged) + +signals: + void applyToChildrenChanged(); + void overwriteDivergedChanged(); + +public: + enum Roles + { + DisplayName, + AvatarUrl, + IsSpace, + IsEditable, + IsDifferentFromBase, + IsAlreadyUpToDate, + ApplyPermissions, + }; + + explicit PowerlevelsSpacesListModel(const std::string &room_id_, + const mtx::events::state::PowerLevels &pl, + QObject *parent = nullptr); + + QHash<int, QByteArray> roleNames() const override; + int rowCount(const QModelIndex &) const override { return static_cast<int>(spaces.size()); } + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + bool + setData(const QModelIndex &index, const QVariant &value, int role = Qt::DisplayRole) override; + + bool applyToChildren() const { return applyToChildren_; } + bool overwriteDiverged() const { return overwriteDiverged_; } + + void setApplyToChildren(bool val) + { + applyToChildren_ = val; + emit applyToChildrenChanged(); + updateToDefaults(); + } + void setOverwriteDiverged(bool val) + { + overwriteDiverged_ = val; + emit overwriteDivergedChanged(); + updateToDefaults(); + } + + void updateToDefaults(); + + Q_INVOKABLE void commit(); + + struct Entry + { + ~Entry() = default; + + std::string roomid; + mtx::events::state::PowerLevels pl; + bool apply = false; + }; + + std::string room_id; + QVector<Entry> spaces; + mtx::events::state::PowerLevels oldPowerLevels_, newPowerlevels_; + + bool applyToChildren_ = true, overwriteDiverged_ = false; +}; + class PowerlevelEditingModels : public QObject { Q_OBJECT Q_PROPERTY(PowerlevelsUserListModel *users READ users CONSTANT) Q_PROPERTY(PowerlevelsTypeListModel *types READ types CONSTANT) + Q_PROPERTY(PowerlevelsSpacesListModel *spaces READ spaces CONSTANT) Q_PROPERTY(qlonglong adminLevel READ adminLevel NOTIFY adminLevelChanged) Q_PROPERTY(qlonglong moderatorLevel READ moderatorLevel NOTIFY moderatorLevelChanged) Q_PROPERTY(qlonglong defaultUserLevel READ defaultUserLevel NOTIFY defaultUserLevelChanged) + Q_PROPERTY(bool isSpace READ isSpace CONSTANT) signals: void adminLevelChanged(); void moderatorLevelChanged(); void defaultUserLevelChanged(); +private: + mtx::events::state::PowerLevels calculateNewPowerlevel() const; + public: explicit PowerlevelEditingModels(QString room_id, QObject *parent = nullptr); PowerlevelsUserListModel *users() { return &users_; } PowerlevelsTypeListModel *types() { return &types_; } + PowerlevelsSpacesListModel *spaces() { return &spaces_; } qlonglong adminLevel() const { return powerLevels_.state_level(to_string(mtx::events::EventType::RoomPowerLevels)); } qlonglong moderatorLevel() const { return powerLevels_.redact; } qlonglong defaultUserLevel() const { return powerLevels_.users_default; } + bool isSpace() const; Q_INVOKABLE void commit(); + Q_INVOKABLE void updateSpacesModel(); Q_INVOKABLE void addRole(int pl); mtx::events::state::PowerLevels powerLevels_; PowerlevelsTypeListModel types_; PowerlevelsUserListModel users_; + PowerlevelsSpacesListModel spaces_; std::string room_id_; }; |