diff options
author | Nicolas Werner <nicolas.werner@hotmail.de> | 2023-07-05 00:08:37 +0200 |
---|---|---|
committer | Nicolas Werner <nicolas.werner@hotmail.de> | 2023-07-05 00:08:37 +0200 |
commit | ad6e4fef6407b9e39ab8ee329b4e8c12376c8494 (patch) | |
tree | bfa2634aabea625a1479817700c35d846273371b /src | |
parent | Add some event expiration function (diff) | |
download | nheko-ad6e4fef6407b9e39ab8ee329b4e8c12376c8494.tar.xz |
Add experimental event expiration
Currently disabled by default.
Diffstat (limited to 'src')
-rw-r--r-- | src/ChatPage.cpp | 1 | ||||
-rw-r--r-- | src/UserSettingsPage.cpp | 33 | ||||
-rw-r--r-- | src/UserSettingsPage.h | 6 | ||||
-rw-r--r-- | src/Utils.cpp | 84 | ||||
-rw-r--r-- | src/ui/EventExpiry.cpp | 124 | ||||
-rw-r--r-- | src/ui/EventExpiry.h | 67 |
6 files changed, 285 insertions, 30 deletions
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index c305a54a..4686b0f5 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -87,6 +87,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QObject *parent) if (lastSpacesUpdate < QDateTime::currentDateTime().addSecs(-20 * 60)) { lastSpacesUpdate = QDateTime::currentDateTime(); utils::updateSpaceVias(); + utils::removeExpiredEvents(); } if (!isConnected_) diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index ea7f22c4..7c30f877 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -101,6 +101,8 @@ UserSettings::load(std::optional<QString> profile) exposeDBusApi_ = settings.value(QStringLiteral("user/expose_dbus_api"), false).toBool(); updateSpaceVias_ = settings.value(QStringLiteral("user/space_background_maintenance"), true).toBool(); + expireEvents_ = + settings.value(QStringLiteral("user/expired_events_background_maintenance"), false).toBool(); mobileMode_ = settings.value(QStringLiteral("user/mobile_mode"), false).toBool(); emojiFont_ = settings.value(QStringLiteral("user/emoji_font_family"), "emoji").toString(); @@ -309,6 +311,17 @@ UserSettings::setUpdateSpaceVias(bool state) } void +UserSettings::setExpireEvents(bool state) +{ + if (expireEvents_ == state) + return; + + expireEvents_ = state; + emit expireEventsChanged(state); + save(); +} + +void UserSettings::setMarkdown(bool state) { if (state == markdown_) @@ -924,6 +937,7 @@ UserSettings::save() settings.setValue(QStringLiteral("open_video_external"), openVideoExternal_); settings.setValue(QStringLiteral("expose_dbus_api"), exposeDBusApi_); settings.setValue(QStringLiteral("space_background_maintenance"), updateSpaceVias_); + settings.setValue(QStringLiteral("expired_events_background_maintenance"), expireEvents_); settings.endGroup(); // user @@ -1129,6 +1143,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const return tr("Expose room information via D-Bus"); case UpdateSpaceVias: return tr("Periodically update community routing information"); + case ExpireEvents: + return tr("Periodically delete expired events"); } } else if (role == Value) { switch (index.row()) { @@ -1266,6 +1282,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const return i->exposeDBusApi(); case UpdateSpaceVias: return i->updateSpaceVias(); + case ExpireEvents: + return i->expireEvents(); } } else if (role == Description) { switch (index.row()) { @@ -1449,6 +1467,10 @@ UserSettingsModel::data(const QModelIndex &index, int role) const "information about what servers participate in a room to community members. Since " "the room participants can change over time, this needs to be updated from time to " "time. This setting enables a background job to do that automatically."); + case ExpireEvents: + return tr("Regularly redact expired events as specified in the event expiration " + "configuration. Since this is currently not executed server side, you need " + "to have one client running this regularly."); } } else if (role == Type) { switch (index.row()) { @@ -1499,6 +1521,7 @@ UserSettingsModel::data(const QModelIndex &index, int role) const case UseOnlineKeyBackup: case ExposeDBusApi: case UpdateSpaceVias: + case ExpireEvents: case SpaceNotifications: case FancyEffects: case ReducedMotion: @@ -1994,6 +2017,13 @@ UserSettingsModel::setData(const QModelIndex &index, const QVariant &value, int } else return false; } + case ExpireEvents: { + if (value.userType() == QMetaType::Bool) { + i->setExpireEvents(value.toBool()); + return true; + } else + return false; + } } } return false; @@ -2249,4 +2279,7 @@ UserSettingsModel::UserSettingsModel(QObject *p) connect(s.get(), &UserSettings::updateSpaceViasChanged, this, [this] { emit dataChanged(index(UpdateSpaceVias), index(UpdateSpaceVias), {Value}); }); + connect(s.get(), &UserSettings::expireEventsChanged, this, [this] { + emit dataChanged(index(ExpireEvents), index(ExpireEvents), {Value}); + }); } diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h index 34dae2ea..4e2691e5 100644 --- a/src/UserSettingsPage.h +++ b/src/UserSettingsPage.h @@ -128,6 +128,7 @@ class UserSettings final : public QObject bool exposeDBusApi READ exposeDBusApi WRITE setExposeDBusApi NOTIFY exposeDBusApiChanged) Q_PROPERTY(bool updateSpaceVias READ updateSpaceVias WRITE setUpdateSpaceVias NOTIFY updateSpaceViasChanged) + Q_PROPERTY(bool expireEvents READ expireEvents WRITE setExpireEvents NOTIFY expireEventsChanged) UserSettings(); @@ -233,6 +234,7 @@ public: void setCollapsedSpaces(QList<QStringList> spaces); void setExposeDBusApi(bool state); void setUpdateSpaceVias(bool state); + void setExpireEvents(bool state); QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; } bool messageHoverHighlight() const { return messageHoverHighlight_; } @@ -308,6 +310,7 @@ public: QList<QStringList> collapsedSpaces() const { return collapsedSpaces_; } bool exposeDBusApi() const { return exposeDBusApi_; } bool updateSpaceVias() const { return updateSpaceVias_; } + bool expireEvents() const { return expireEvents_; } signals: void groupViewStateChanged(bool state); @@ -372,6 +375,7 @@ signals: void recentReactionsChanged(); void exposeDBusApiChanged(bool state); void updateSpaceViasChanged(bool state); + void expireEventsChanged(bool state); private: // Default to system theme if QT_QPA_PLATFORMTHEME var is set. @@ -446,6 +450,7 @@ private: bool openVideoExternal_; bool exposeDBusApi_; bool updateSpaceVias_; + bool expireEvents_; QSettings settings; @@ -478,6 +483,7 @@ class UserSettingsModel : public QAbstractListModel ExposeDBusApi, #endif UpdateSpaceVias, + ExpireEvents, AccessibilitySection, ReducedMotion, diff --git a/src/Utils.cpp b/src/Utils.cpp index 7a412db0..663609fe 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -1610,8 +1610,7 @@ std::atomic<bool> event_expiration_running = false; void utils::removeExpiredEvents() { - // TODO(Nico): Add its own toggle... - if (!UserSettings::instance()->updateSpaceVias()) + if (!UserSettings::instance()->expireEvents()) return; if (event_expiration_running.exchange(true)) { @@ -1645,18 +1644,20 @@ utils::removeExpiredEvents() std::string currentRoom; std::uint64_t currentRoomCount = 0; std::string currentRoomPrevToken; + std::set<std::pair<std::string, std::string>> currentRoomStateEvents; std::vector<std::string> currentRoomRedactionQueue; mtx::events::account_data::nheko_extensions::EventExpiry currentExpiry; static void next(std::shared_ptr<ApplyEventExpiration> state) { if (!state->currentRoomRedactionQueue.empty()) { + auto evid = state->currentRoomRedactionQueue.back(); + auto room = state->currentRoom; http::client()->redact_event( - state->currentRoom, - state->currentRoomRedactionQueue.back(), - [state = std::move(state)](const mtx::responses::EventId &, - mtx::http::RequestErr e) mutable { - const auto &event_id = state->currentRoomRedactionQueue.back(); + room, + evid, + [state = std::move(state), evid](const mtx::responses::EventId &, + mtx::http::RequestErr e) mutable { if (e) { if (e->status_code == 429 && e->matrix_error.retry_after.count() != 0) { ChatPage::instance()->callFunctionOnGuiThread( @@ -1669,17 +1670,19 @@ utils::removeExpiredEvents() }); }); return; + } else { + nhlog::net()->error("Failed to redact event {} in {}: {}", + evid, + state->currentRoom, + *e); + state->currentRoomRedactionQueue.pop_back(); + next(std::move(state)); } - - nhlog::net()->error("Failed to redact event {} in {}: {}", - event_id, - state->currentRoom, - *e); + } else { + nhlog::net()->info("Redacted event {} in {}", evid, state->currentRoom); + state->currentRoomRedactionQueue.pop_back(); + next(std::move(state)); } - nhlog::net()->info( - "Redacted event {} in {}: {}", event_id, state->currentRoom, *e); - state->currentRoomRedactionQueue.pop_back(); - next(std::move(state)); }); } else if (!state->currentRoom.empty()) { mtx::http::MessagesOpts opts{}; @@ -1687,6 +1690,7 @@ utils::removeExpiredEvents() opts.from = state->currentRoomPrevToken; opts.limit = 1000; opts.filter = state->filter; + opts.room_id = state->currentRoom; http::client()->messages( opts, @@ -1708,6 +1712,19 @@ utils::removeExpiredEvents() mtx::events::RedactionEvent<mtx::events::msg::Redaction>>(e)) continue; + if (std::holds_alternative< + mtx::events::RoomEvent<mtx::events::msg::Redacted>>(e)) + continue; + + if (std::holds_alternative< + mtx::events::StateEvent<mtx::events::msg::Redacted>>(e)) + continue; + + // skip events we don't know to protect us from mistakes. + if (std::holds_alternative< + mtx::events::RoomEvent<mtx::events::Unknown>>(e)) + continue; + if (mtx::accessors::sender(e) != us) continue; @@ -1720,6 +1737,21 @@ utils::removeExpiredEvents() mtx::accessors::is_state_event(e)) continue; + if (mtx::accessors::is_state_event(e)) { + // skip the first state event of a type + if (std::visit( + [&state](const auto &se) { + if constexpr (requires { se.state_key; }) + return state->currentRoomStateEvents + .emplace(to_string(se.type), se.state_key) + .second; + else + return false; + }, + e)) + continue; + } + if (state->currentExpiry.keep_only_latest && state->currentRoomCount > state->currentExpiry.keep_only_latest) { state->currentRoomRedactionQueue.push_back( @@ -1738,6 +1770,7 @@ utils::removeExpiredEvents() state->currentRoom.clear(); state->currentRoomCount = 0; state->currentRoomPrevToken.clear(); + state->currentRoomStateEvents.clear(); } next(std::move(state)); @@ -1764,20 +1797,11 @@ utils::removeExpiredEvents() auto asus = std::make_shared<ApplyEventExpiration>(); - asus->filter = - nlohmann::json{ - "room", - nlohmann::json::object({ - { - "timeline", - nlohmann::json::object({ - {"senders", nlohmann::json::array({us})}, - {"not_types", nlohmann::json::array({"m.room.redaction"})}, - }), - }, - }), - } - .dump(); + nlohmann::json filter; + filter["timeline"]["senders"] = nlohmann::json::array({us}); + filter["timeline"]["not_types"] = nlohmann::json::array({"m.room.redaction"}); + + asus->filter = filter.dump(); asus->globalExpiry = getExpEv(); diff --git a/src/ui/EventExpiry.cpp b/src/ui/EventExpiry.cpp new file mode 100644 index 00000000..ca149dc3 --- /dev/null +++ b/src/ui/EventExpiry.cpp @@ -0,0 +1,124 @@ +// SPDX-FileCopyrightText: Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "EventExpiry.h" + +#include "Cache_p.h" +#include "MainWindow.h" +#include "MatrixClient.h" +#include "timeline/TimelineModel.h" + +void +EventExpiry::load() +{ + using namespace mtx::events; + + this->event = {}; + + if (auto temp = cache::client()->getAccountData(mtx::events::EventType::NhekoEventExpiry, "")) { + auto h = std::get< + mtx::events::AccountDataEvent<mtx::events::account_data::nheko_extensions::EventExpiry>>( + *temp); + this->event = std::move(h.content); + } + + if (!roomid_.isEmpty()) { + if (auto temp = cache::client()->getAccountData(mtx::events::EventType::NhekoEventExpiry, + roomid_.toStdString())) { + auto h = std::get<mtx::events::AccountDataEvent< + mtx::events::account_data::nheko_extensions::EventExpiry>>(*temp); + this->event = std::move(h.content); + } + } + + emit expireEventsAfterDaysChanged(); + emit expireEventsAfterCountChanged(); + emit protectLatestEventsChanged(); + emit expireStateEventsChanged(); +} + +void +EventExpiry::save() +{ + if (roomid_.isEmpty()) + http::client()->put_account_data(event, [](mtx::http::RequestErr e) { + if (e) { + nhlog::net()->error("Failed to set hidden events: {}", *e); + MainWindow::instance()->showNotification( + tr("Failed to set hidden events: %1") + .arg(QString::fromStdString(e->matrix_error.error))); + } + }); + else + http::client()->put_room_account_data( + roomid_.toStdString(), event, [](mtx::http::RequestErr e) { + if (e) { + nhlog::net()->error("Failed to set hidden events: {}", *e); + MainWindow::instance()->showNotification( + tr("Failed to set hidden events: %1") + .arg(QString::fromStdString(e->matrix_error.error))); + } + }); +} + +int +EventExpiry::expireEventsAfterDays() const +{ + return event.expire_after_ms / (1000 * 60 * 60 * 24); +} + +int +EventExpiry::expireEventsAfterCount() const +{ + return event.keep_only_latest; +} + +int +EventExpiry::protectLatestEvents() const +{ + return event.protect_latest; +} + +bool +EventExpiry::expireStateEvents() const +{ + return !event.exclude_state_events; +} + +void +EventExpiry::setExpireEventsAfterDays(int val) +{ + if (val > 0) + this->event.expire_after_ms = val * (1000 * 60 * 60 * 24); + else + this->event.expire_after_ms = 0; + emit expireEventsAfterDaysChanged(); +} + +void +EventExpiry::setProtectLatestEvents(int val) +{ + if (val > 0) + this->event.protect_latest = val; + else + this->event.expire_after_ms = 0; + emit protectLatestEventsChanged(); +} + +void +EventExpiry::setExpireEventsAfterCount(int val) +{ + if (val > 0) + this->event.keep_only_latest = val; + else + this->event.keep_only_latest = 0; + emit expireEventsAfterCountChanged(); +} + +void +EventExpiry::setExpireStateEvents(bool val) +{ + this->event.exclude_state_events = !val; + emit expireEventsAfterCountChanged(); +} diff --git a/src/ui/EventExpiry.h b/src/ui/EventExpiry.h new file mode 100644 index 00000000..aa144dc3 --- /dev/null +++ b/src/ui/EventExpiry.h @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <QObject> +#include <QQmlEngine> +#include <QString> +#include <QVariantList> + +#include <mtx/events/nheko_extensions/event_expiry.hpp> + +class EventExpiry : public QObject +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(QString roomid READ roomid WRITE setRoomid NOTIFY roomidChanged REQUIRED) + Q_PROPERTY(int expireEventsAfterDays READ expireEventsAfterDays WRITE setExpireEventsAfterDays + NOTIFY expireEventsAfterDaysChanged) + Q_PROPERTY(bool expireStateEvents READ expireStateEvents WRITE setExpireStateEvents NOTIFY + expireStateEventsChanged) + Q_PROPERTY(int expireEventsAfterCount READ expireEventsAfterCount WRITE + setExpireEventsAfterCount NOTIFY expireEventsAfterCountChanged) + Q_PROPERTY(int protectLatestEvents READ protectLatestEvents WRITE setProtectLatestEvents NOTIFY + protectLatestEventsChanged) +public: + explicit EventExpiry(QObject *p = nullptr) + : QObject(p) + { + } + + Q_INVOKABLE void save(); + + [[nodiscard]] QString roomid() const { return roomid_; } + void setRoomid(const QString &r) + { + roomid_ = r; + emit roomidChanged(); + + load(); + } + + [[nodiscard]] int expireEventsAfterDays() const; + [[nodiscard]] int expireEventsAfterCount() const; + [[nodiscard]] int protectLatestEvents() const; + [[nodiscard]] bool expireStateEvents() const; + void setExpireEventsAfterDays(int); + void setExpireEventsAfterCount(int); + void setProtectLatestEvents(int); + void setExpireStateEvents(bool); + +signals: + void roomidChanged(); + + void expireEventsAfterDaysChanged(); + void expireEventsAfterCountChanged(); + void protectLatestEventsChanged(); + void expireStateEventsChanged(); + +private: + QString roomid_; + mtx::events::account_data::nheko_extensions::EventExpiry event = {}; + + void load(); +}; + |