mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-25 12:38:48 +03:00
Add experimental event expiration
Currently disabled by default.
This commit is contained in:
parent
dcb6c00708
commit
ad6e4fef64
10 changed files with 475 additions and 32 deletions
|
@ -387,6 +387,8 @@ set(SRC_FILES
|
||||||
# UI components
|
# UI components
|
||||||
src/ui/HiddenEvents.cpp
|
src/ui/HiddenEvents.cpp
|
||||||
src/ui/HiddenEvents.h
|
src/ui/HiddenEvents.h
|
||||||
|
src/ui/EventExpiry.cpp
|
||||||
|
src/ui/EventExpiry.h
|
||||||
src/ui/MxcAnimatedImage.cpp
|
src/ui/MxcAnimatedImage.cpp
|
||||||
src/ui/MxcAnimatedImage.h
|
src/ui/MxcAnimatedImage.h
|
||||||
src/ui/MxcMediaProxy.cpp
|
src/ui/MxcMediaProxy.cpp
|
||||||
|
@ -599,7 +601,7 @@ if(USE_BUNDLED_MTXCLIENT)
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
MatrixClient
|
MatrixClient
|
||||||
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
|
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
|
||||||
GIT_TAG f4425af712afc6ad704a39b93c912432bd3c1914
|
GIT_TAG 0a4cc9421a97bea81a8921f3f5e040f0a34278fc
|
||||||
)
|
)
|
||||||
set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
|
set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
|
||||||
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
|
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
|
||||||
|
@ -763,6 +765,7 @@ set(QML_SOURCES
|
||||||
resources/qml/dialogs/CreateDirect.qml
|
resources/qml/dialogs/CreateDirect.qml
|
||||||
resources/qml/dialogs/CreateRoom.qml
|
resources/qml/dialogs/CreateRoom.qml
|
||||||
resources/qml/dialogs/HiddenEventsDialog.qml
|
resources/qml/dialogs/HiddenEventsDialog.qml
|
||||||
|
resources/qml/dialogs/EventExpirationDialog.qml
|
||||||
resources/qml/dialogs/ImageOverlay.qml
|
resources/qml/dialogs/ImageOverlay.qml
|
||||||
resources/qml/dialogs/ImagePackEditorDialog.qml
|
resources/qml/dialogs/ImagePackEditorDialog.qml
|
||||||
resources/qml/dialogs/ImagePackSettingsDialog.qml
|
resources/qml/dialogs/ImagePackSettingsDialog.qml
|
||||||
|
|
|
@ -214,7 +214,7 @@ modules:
|
||||||
buildsystem: cmake-ninja
|
buildsystem: cmake-ninja
|
||||||
name: mtxclient
|
name: mtxclient
|
||||||
sources:
|
sources:
|
||||||
- commit: f4425af712afc6ad704a39b93c912432bd3c1914
|
- commit: 0a4cc9421a97bea81a8921f3f5e040f0a34278fc
|
||||||
#tag: v0.9.2
|
#tag: v0.9.2
|
||||||
type: git
|
type: git
|
||||||
url: https://github.com/Nheko-Reborn/mtxclient.git
|
url: https://github.com/Nheko-Reborn/mtxclient.git
|
||||||
|
|
167
resources/qml/dialogs/EventExpirationDialog.qml
Normal file
167
resources/qml/dialogs/EventExpirationDialog.qml
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
// SPDX-FileCopyrightText: Nheko Contributors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import ".."
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import im.nheko
|
||||||
|
|
||||||
|
ApplicationWindow {
|
||||||
|
id: dialog
|
||||||
|
|
||||||
|
property string roomid: ""
|
||||||
|
property string roomName: ""
|
||||||
|
property var onAccepted: undefined
|
||||||
|
|
||||||
|
modality: Qt.NonModal
|
||||||
|
flags: Qt.Dialog | Qt.WindowTitleHint
|
||||||
|
width: 275
|
||||||
|
height: 330
|
||||||
|
minimumWidth: 250
|
||||||
|
minimumHeight: 220
|
||||||
|
|
||||||
|
EventExpiry {
|
||||||
|
id: eventExpiry
|
||||||
|
|
||||||
|
roomid: dialog.roomid
|
||||||
|
}
|
||||||
|
|
||||||
|
title: {
|
||||||
|
if (roomid) {
|
||||||
|
return qsTr("Event expiration for %1").arg(roomName);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return qsTr("Event expiration");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Shortcut {
|
||||||
|
sequence: StandardKey.Cancel
|
||||||
|
onActivated: dbb.rejected()
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: Nheko.paddingMedium
|
||||||
|
anchors.margins: Nheko.paddingMedium
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
MatrixText {
|
||||||
|
id: promptLabel
|
||||||
|
text: {
|
||||||
|
if (roomid) {
|
||||||
|
return qsTr("You can configure when your messages will be deleted in %1. This only happens when Nheko is open and has permissions to delete messages until Matrix servers support this feature natively. In general 0 means disable.").arg(roomName);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return qsTr("You can configure when your messages will be deleted in all rooms unless configured otherwise. This only happens when Nheko is open and has permissions to delete messages until Matrix servers support this feature natively. In general 0 means disable.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.2)
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: false
|
||||||
|
}
|
||||||
|
|
||||||
|
GridLayout {
|
||||||
|
columns: 2
|
||||||
|
rowSpacing: Nheko.paddingMedium
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
MatrixText {
|
||||||
|
text: qsTr("Expire events after X days")
|
||||||
|
ToolTip.text: qsTr("Automatically redacts messages after X days, unless otherwise protected. Set to 0 to disable.")
|
||||||
|
ToolTip.visible: hh1.hovered
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
HoverHandler {
|
||||||
|
id: hh1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SpinBox {
|
||||||
|
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||||
|
from: 0
|
||||||
|
to: 1000
|
||||||
|
stepSize: 1
|
||||||
|
value: eventExpiry.expireEventsAfterDays
|
||||||
|
onValueChanged: eventExpiry.expireEventsAfterDays = value
|
||||||
|
editable: true
|
||||||
|
}
|
||||||
|
|
||||||
|
MatrixText {
|
||||||
|
text: qsTr("Only keep latest X events")
|
||||||
|
ToolTip.text: qsTr("Deletes your events in this room if there are more than X newer messages unless otherwise protected. Set to 0 to disable.")
|
||||||
|
ToolTip.visible: hh2.hovered
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
HoverHandler {
|
||||||
|
id: hh2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SpinBox {
|
||||||
|
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||||
|
from: 0
|
||||||
|
to: 1000
|
||||||
|
stepSize: 1
|
||||||
|
value: eventExpiry.expireEventsAfterCount
|
||||||
|
onValueChanged: eventExpiry.expireEventsAfterCount = value
|
||||||
|
editable: true
|
||||||
|
}
|
||||||
|
|
||||||
|
MatrixText {
|
||||||
|
text: qsTr("Always keep latest X events")
|
||||||
|
ToolTip.text: qsTr("This prevents events to be deleted by the above 2 settings if they are the latest X messages from you in the room.")
|
||||||
|
ToolTip.visible: hh3.hovered
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
HoverHandler {
|
||||||
|
id: hh3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SpinBox {
|
||||||
|
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||||
|
from: 0
|
||||||
|
to: 1000
|
||||||
|
stepSize: 1
|
||||||
|
value: eventExpiry.protectLatestEvents
|
||||||
|
onValueChanged: eventExpiry.protectLatestEvents = value
|
||||||
|
editable: true
|
||||||
|
}
|
||||||
|
|
||||||
|
MatrixText {
|
||||||
|
text: qsTr("Include state events")
|
||||||
|
ToolTip.text: qsTr("If this is turned on, old state events also get redacted. The latest state event of any type+key combination is excluded from redaction to not remove the room name and similar state by accident.")
|
||||||
|
ToolTip.visible: hh4.hovered
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
HoverHandler {
|
||||||
|
id: hh4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ToggleButton {
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
checked: eventExpiry.expireStateEvents
|
||||||
|
onToggled: eventExpiry.expireStateEvents = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
footer: DialogButtonBox {
|
||||||
|
id: dbb
|
||||||
|
|
||||||
|
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
|
||||||
|
onAccepted: {
|
||||||
|
eventExpiry.save();
|
||||||
|
dialog.close();
|
||||||
|
}
|
||||||
|
onRejected: dialog.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -501,6 +501,24 @@ ApplicationWindow {
|
||||||
Layout.alignment: Qt.AlignRight
|
Layout.alignment: Qt.AlignRight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("Automatic event deletion")
|
||||||
|
color: palette.text
|
||||||
|
}
|
||||||
|
|
||||||
|
EventExpirationDialog {
|
||||||
|
id: eventExpirationDialog
|
||||||
|
roomid: roomSettings.roomId
|
||||||
|
roomName: roomSettings.roomName
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: qsTr("Configure")
|
||||||
|
ToolTip.text: qsTr("Select if your events get automatically deleted in this room.")
|
||||||
|
onClicked: eventExpirationDialog.show()
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
text: qsTr("GENERAL SETTINGS")
|
text: qsTr("GENERAL SETTINGS")
|
||||||
font.bold: true
|
font.bold: true
|
||||||
|
|
|
@ -87,6 +87,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QObject *parent)
|
||||||
if (lastSpacesUpdate < QDateTime::currentDateTime().addSecs(-20 * 60)) {
|
if (lastSpacesUpdate < QDateTime::currentDateTime().addSecs(-20 * 60)) {
|
||||||
lastSpacesUpdate = QDateTime::currentDateTime();
|
lastSpacesUpdate = QDateTime::currentDateTime();
|
||||||
utils::updateSpaceVias();
|
utils::updateSpaceVias();
|
||||||
|
utils::removeExpiredEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isConnected_)
|
if (!isConnected_)
|
||||||
|
|
|
@ -101,6 +101,8 @@ UserSettings::load(std::optional<QString> profile)
|
||||||
exposeDBusApi_ = settings.value(QStringLiteral("user/expose_dbus_api"), false).toBool();
|
exposeDBusApi_ = settings.value(QStringLiteral("user/expose_dbus_api"), false).toBool();
|
||||||
updateSpaceVias_ =
|
updateSpaceVias_ =
|
||||||
settings.value(QStringLiteral("user/space_background_maintenance"), true).toBool();
|
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();
|
mobileMode_ = settings.value(QStringLiteral("user/mobile_mode"), false).toBool();
|
||||||
emojiFont_ = settings.value(QStringLiteral("user/emoji_font_family"), "emoji").toString();
|
emojiFont_ = settings.value(QStringLiteral("user/emoji_font_family"), "emoji").toString();
|
||||||
|
@ -308,6 +310,17 @@ UserSettings::setUpdateSpaceVias(bool state)
|
||||||
save();
|
save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
UserSettings::setExpireEvents(bool state)
|
||||||
|
{
|
||||||
|
if (expireEvents_ == state)
|
||||||
|
return;
|
||||||
|
|
||||||
|
expireEvents_ = state;
|
||||||
|
emit expireEventsChanged(state);
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
UserSettings::setMarkdown(bool state)
|
UserSettings::setMarkdown(bool state)
|
||||||
{
|
{
|
||||||
|
@ -924,6 +937,7 @@ UserSettings::save()
|
||||||
settings.setValue(QStringLiteral("open_video_external"), openVideoExternal_);
|
settings.setValue(QStringLiteral("open_video_external"), openVideoExternal_);
|
||||||
settings.setValue(QStringLiteral("expose_dbus_api"), exposeDBusApi_);
|
settings.setValue(QStringLiteral("expose_dbus_api"), exposeDBusApi_);
|
||||||
settings.setValue(QStringLiteral("space_background_maintenance"), updateSpaceVias_);
|
settings.setValue(QStringLiteral("space_background_maintenance"), updateSpaceVias_);
|
||||||
|
settings.setValue(QStringLiteral("expired_events_background_maintenance"), expireEvents_);
|
||||||
|
|
||||||
settings.endGroup(); // user
|
settings.endGroup(); // user
|
||||||
|
|
||||||
|
@ -1129,6 +1143,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
|
||||||
return tr("Expose room information via D-Bus");
|
return tr("Expose room information via D-Bus");
|
||||||
case UpdateSpaceVias:
|
case UpdateSpaceVias:
|
||||||
return tr("Periodically update community routing information");
|
return tr("Periodically update community routing information");
|
||||||
|
case ExpireEvents:
|
||||||
|
return tr("Periodically delete expired events");
|
||||||
}
|
}
|
||||||
} else if (role == Value) {
|
} else if (role == Value) {
|
||||||
switch (index.row()) {
|
switch (index.row()) {
|
||||||
|
@ -1266,6 +1282,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
|
||||||
return i->exposeDBusApi();
|
return i->exposeDBusApi();
|
||||||
case UpdateSpaceVias:
|
case UpdateSpaceVias:
|
||||||
return i->updateSpaceVias();
|
return i->updateSpaceVias();
|
||||||
|
case ExpireEvents:
|
||||||
|
return i->expireEvents();
|
||||||
}
|
}
|
||||||
} else if (role == Description) {
|
} else if (role == Description) {
|
||||||
switch (index.row()) {
|
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 "
|
"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 "
|
"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.");
|
"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) {
|
} else if (role == Type) {
|
||||||
switch (index.row()) {
|
switch (index.row()) {
|
||||||
|
@ -1499,6 +1521,7 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
|
||||||
case UseOnlineKeyBackup:
|
case UseOnlineKeyBackup:
|
||||||
case ExposeDBusApi:
|
case ExposeDBusApi:
|
||||||
case UpdateSpaceVias:
|
case UpdateSpaceVias:
|
||||||
|
case ExpireEvents:
|
||||||
case SpaceNotifications:
|
case SpaceNotifications:
|
||||||
case FancyEffects:
|
case FancyEffects:
|
||||||
case ReducedMotion:
|
case ReducedMotion:
|
||||||
|
@ -1994,6 +2017,13 @@ UserSettingsModel::setData(const QModelIndex &index, const QVariant &value, int
|
||||||
} else
|
} else
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
case ExpireEvents: {
|
||||||
|
if (value.userType() == QMetaType::Bool) {
|
||||||
|
i->setExpireEvents(value.toBool());
|
||||||
|
return true;
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -2249,4 +2279,7 @@ UserSettingsModel::UserSettingsModel(QObject *p)
|
||||||
connect(s.get(), &UserSettings::updateSpaceViasChanged, this, [this] {
|
connect(s.get(), &UserSettings::updateSpaceViasChanged, this, [this] {
|
||||||
emit dataChanged(index(UpdateSpaceVias), index(UpdateSpaceVias), {Value});
|
emit dataChanged(index(UpdateSpaceVias), index(UpdateSpaceVias), {Value});
|
||||||
});
|
});
|
||||||
|
connect(s.get(), &UserSettings::expireEventsChanged, this, [this] {
|
||||||
|
emit dataChanged(index(ExpireEvents), index(ExpireEvents), {Value});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,6 +128,7 @@ class UserSettings final : public QObject
|
||||||
bool exposeDBusApi READ exposeDBusApi WRITE setExposeDBusApi NOTIFY exposeDBusApiChanged)
|
bool exposeDBusApi READ exposeDBusApi WRITE setExposeDBusApi NOTIFY exposeDBusApiChanged)
|
||||||
Q_PROPERTY(bool updateSpaceVias READ updateSpaceVias WRITE setUpdateSpaceVias NOTIFY
|
Q_PROPERTY(bool updateSpaceVias READ updateSpaceVias WRITE setUpdateSpaceVias NOTIFY
|
||||||
updateSpaceViasChanged)
|
updateSpaceViasChanged)
|
||||||
|
Q_PROPERTY(bool expireEvents READ expireEvents WRITE setExpireEvents NOTIFY expireEventsChanged)
|
||||||
|
|
||||||
UserSettings();
|
UserSettings();
|
||||||
|
|
||||||
|
@ -233,6 +234,7 @@ public:
|
||||||
void setCollapsedSpaces(QList<QStringList> spaces);
|
void setCollapsedSpaces(QList<QStringList> spaces);
|
||||||
void setExposeDBusApi(bool state);
|
void setExposeDBusApi(bool state);
|
||||||
void setUpdateSpaceVias(bool state);
|
void setUpdateSpaceVias(bool state);
|
||||||
|
void setExpireEvents(bool state);
|
||||||
|
|
||||||
QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; }
|
QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; }
|
||||||
bool messageHoverHighlight() const { return messageHoverHighlight_; }
|
bool messageHoverHighlight() const { return messageHoverHighlight_; }
|
||||||
|
@ -308,6 +310,7 @@ public:
|
||||||
QList<QStringList> collapsedSpaces() const { return collapsedSpaces_; }
|
QList<QStringList> collapsedSpaces() const { return collapsedSpaces_; }
|
||||||
bool exposeDBusApi() const { return exposeDBusApi_; }
|
bool exposeDBusApi() const { return exposeDBusApi_; }
|
||||||
bool updateSpaceVias() const { return updateSpaceVias_; }
|
bool updateSpaceVias() const { return updateSpaceVias_; }
|
||||||
|
bool expireEvents() const { return expireEvents_; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void groupViewStateChanged(bool state);
|
void groupViewStateChanged(bool state);
|
||||||
|
@ -372,6 +375,7 @@ signals:
|
||||||
void recentReactionsChanged();
|
void recentReactionsChanged();
|
||||||
void exposeDBusApiChanged(bool state);
|
void exposeDBusApiChanged(bool state);
|
||||||
void updateSpaceViasChanged(bool state);
|
void updateSpaceViasChanged(bool state);
|
||||||
|
void expireEventsChanged(bool state);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Default to system theme if QT_QPA_PLATFORMTHEME var is set.
|
// Default to system theme if QT_QPA_PLATFORMTHEME var is set.
|
||||||
|
@ -446,6 +450,7 @@ private:
|
||||||
bool openVideoExternal_;
|
bool openVideoExternal_;
|
||||||
bool exposeDBusApi_;
|
bool exposeDBusApi_;
|
||||||
bool updateSpaceVias_;
|
bool updateSpaceVias_;
|
||||||
|
bool expireEvents_;
|
||||||
|
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
|
|
||||||
|
@ -478,6 +483,7 @@ class UserSettingsModel : public QAbstractListModel
|
||||||
ExposeDBusApi,
|
ExposeDBusApi,
|
||||||
#endif
|
#endif
|
||||||
UpdateSpaceVias,
|
UpdateSpaceVias,
|
||||||
|
ExpireEvents,
|
||||||
|
|
||||||
AccessibilitySection,
|
AccessibilitySection,
|
||||||
ReducedMotion,
|
ReducedMotion,
|
||||||
|
|
|
@ -1610,8 +1610,7 @@ std::atomic<bool> event_expiration_running = false;
|
||||||
void
|
void
|
||||||
utils::removeExpiredEvents()
|
utils::removeExpiredEvents()
|
||||||
{
|
{
|
||||||
// TODO(Nico): Add its own toggle...
|
if (!UserSettings::instance()->expireEvents())
|
||||||
if (!UserSettings::instance()->updateSpaceVias())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (event_expiration_running.exchange(true)) {
|
if (event_expiration_running.exchange(true)) {
|
||||||
|
@ -1645,18 +1644,20 @@ utils::removeExpiredEvents()
|
||||||
std::string currentRoom;
|
std::string currentRoom;
|
||||||
std::uint64_t currentRoomCount = 0;
|
std::uint64_t currentRoomCount = 0;
|
||||||
std::string currentRoomPrevToken;
|
std::string currentRoomPrevToken;
|
||||||
|
std::set<std::pair<std::string, std::string>> currentRoomStateEvents;
|
||||||
std::vector<std::string> currentRoomRedactionQueue;
|
std::vector<std::string> currentRoomRedactionQueue;
|
||||||
mtx::events::account_data::nheko_extensions::EventExpiry currentExpiry;
|
mtx::events::account_data::nheko_extensions::EventExpiry currentExpiry;
|
||||||
|
|
||||||
static void next(std::shared_ptr<ApplyEventExpiration> state)
|
static void next(std::shared_ptr<ApplyEventExpiration> state)
|
||||||
{
|
{
|
||||||
if (!state->currentRoomRedactionQueue.empty()) {
|
if (!state->currentRoomRedactionQueue.empty()) {
|
||||||
|
auto evid = state->currentRoomRedactionQueue.back();
|
||||||
|
auto room = state->currentRoom;
|
||||||
http::client()->redact_event(
|
http::client()->redact_event(
|
||||||
state->currentRoom,
|
room,
|
||||||
state->currentRoomRedactionQueue.back(),
|
evid,
|
||||||
[state = std::move(state)](const mtx::responses::EventId &,
|
[state = std::move(state), evid](const mtx::responses::EventId &,
|
||||||
mtx::http::RequestErr e) mutable {
|
mtx::http::RequestErr e) mutable {
|
||||||
const auto &event_id = state->currentRoomRedactionQueue.back();
|
|
||||||
if (e) {
|
if (e) {
|
||||||
if (e->status_code == 429 && e->matrix_error.retry_after.count() != 0) {
|
if (e->status_code == 429 && e->matrix_error.retry_after.count() != 0) {
|
||||||
ChatPage::instance()->callFunctionOnGuiThread(
|
ChatPage::instance()->callFunctionOnGuiThread(
|
||||||
|
@ -1669,17 +1670,19 @@ utils::removeExpiredEvents()
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
} else {
|
||||||
|
nhlog::net()->error("Failed to redact event {} in {}: {}",
|
||||||
|
evid,
|
||||||
|
state->currentRoom,
|
||||||
|
*e);
|
||||||
|
state->currentRoomRedactionQueue.pop_back();
|
||||||
|
next(std::move(state));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
nhlog::net()->error("Failed to redact event {} in {}: {}",
|
nhlog::net()->info("Redacted event {} in {}", evid, state->currentRoom);
|
||||||
event_id,
|
state->currentRoomRedactionQueue.pop_back();
|
||||||
state->currentRoom,
|
next(std::move(state));
|
||||||
*e);
|
|
||||||
}
|
}
|
||||||
nhlog::net()->info(
|
|
||||||
"Redacted event {} in {}: {}", event_id, state->currentRoom, *e);
|
|
||||||
state->currentRoomRedactionQueue.pop_back();
|
|
||||||
next(std::move(state));
|
|
||||||
});
|
});
|
||||||
} else if (!state->currentRoom.empty()) {
|
} else if (!state->currentRoom.empty()) {
|
||||||
mtx::http::MessagesOpts opts{};
|
mtx::http::MessagesOpts opts{};
|
||||||
|
@ -1687,6 +1690,7 @@ utils::removeExpiredEvents()
|
||||||
opts.from = state->currentRoomPrevToken;
|
opts.from = state->currentRoomPrevToken;
|
||||||
opts.limit = 1000;
|
opts.limit = 1000;
|
||||||
opts.filter = state->filter;
|
opts.filter = state->filter;
|
||||||
|
opts.room_id = state->currentRoom;
|
||||||
|
|
||||||
http::client()->messages(
|
http::client()->messages(
|
||||||
opts,
|
opts,
|
||||||
|
@ -1708,6 +1712,19 @@ utils::removeExpiredEvents()
|
||||||
mtx::events::RedactionEvent<mtx::events::msg::Redaction>>(e))
|
mtx::events::RedactionEvent<mtx::events::msg::Redaction>>(e))
|
||||||
continue;
|
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)
|
if (mtx::accessors::sender(e) != us)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
@ -1720,6 +1737,21 @@ utils::removeExpiredEvents()
|
||||||
mtx::accessors::is_state_event(e))
|
mtx::accessors::is_state_event(e))
|
||||||
continue;
|
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 &&
|
if (state->currentExpiry.keep_only_latest &&
|
||||||
state->currentRoomCount > state->currentExpiry.keep_only_latest) {
|
state->currentRoomCount > state->currentExpiry.keep_only_latest) {
|
||||||
state->currentRoomRedactionQueue.push_back(
|
state->currentRoomRedactionQueue.push_back(
|
||||||
|
@ -1738,6 +1770,7 @@ utils::removeExpiredEvents()
|
||||||
state->currentRoom.clear();
|
state->currentRoom.clear();
|
||||||
state->currentRoomCount = 0;
|
state->currentRoomCount = 0;
|
||||||
state->currentRoomPrevToken.clear();
|
state->currentRoomPrevToken.clear();
|
||||||
|
state->currentRoomStateEvents.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
next(std::move(state));
|
next(std::move(state));
|
||||||
|
@ -1764,20 +1797,11 @@ utils::removeExpiredEvents()
|
||||||
|
|
||||||
auto asus = std::make_shared<ApplyEventExpiration>();
|
auto asus = std::make_shared<ApplyEventExpiration>();
|
||||||
|
|
||||||
asus->filter =
|
nlohmann::json filter;
|
||||||
nlohmann::json{
|
filter["timeline"]["senders"] = nlohmann::json::array({us});
|
||||||
"room",
|
filter["timeline"]["not_types"] = nlohmann::json::array({"m.room.redaction"});
|
||||||
nlohmann::json::object({
|
|
||||||
{
|
asus->filter = filter.dump();
|
||||||
"timeline",
|
|
||||||
nlohmann::json::object({
|
|
||||||
{"senders", nlohmann::json::array({us})},
|
|
||||||
{"not_types", nlohmann::json::array({"m.room.redaction"})},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
.dump();
|
|
||||||
|
|
||||||
asus->globalExpiry = getExpEv();
|
asus->globalExpiry = getExpEv();
|
||||||
|
|
||||||
|
|
124
src/ui/EventExpiry.cpp
Normal file
124
src/ui/EventExpiry.cpp
Normal file
|
@ -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();
|
||||||
|
}
|
67
src/ui/EventExpiry.h
Normal file
67
src/ui/EventExpiry.h
Normal file
|
@ -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();
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue