Evaluate push rules locally

This commit is contained in:
Nicolas Werner 2022-10-13 17:19:54 +02:00
parent a1dd02d763
commit 37009906bb
No known key found for this signature in database
GPG key ID: C8D75E610773F2D9
10 changed files with 180 additions and 234 deletions

View file

@ -1518,10 +1518,6 @@ Cache::updateReadReceipt(lmdb::txn &txn, const std::string &room_id, const Recei
// Append the new ones. // Append the new ones.
for (const auto &[read_by, timestamp] : event_receipts) { for (const auto &[read_by, timestamp] : event_receipts) {
if (read_by == user_id) {
emit removeNotification(QString::fromStdString(room_id),
QString::fromStdString(event_id));
}
saved_receipts.emplace(read_by, timestamp); saved_receipts.emplace(read_by, timestamp);
} }
@ -1818,12 +1814,6 @@ Cache::saveState(const mtx::responses::Sync &res)
updatedInfo.tags.push_back(tag.first); updatedInfo.tags.push_back(tag.first);
} }
} }
if (auto fr = std::get_if<
mtx::events::AccountDataEvent<mtx::events::account_data::FullyRead>>(&evt)) {
nhlog::db()->debug("Fully read: {}", fr->content.event_id);
emit removeNotification(QString::fromStdString(room.first),
QString::fromStdString(fr->content.event_id));
}
} }
} }
@ -2134,27 +2124,6 @@ Cache::roomIds()
return rooms; return rooms;
} }
QMap<QString, mtx::responses::Notifications>
Cache::getTimelineMentions()
{
// TODO: Should be read-only, but getMentionsDb will attempt to create a DB
// if it doesn't exist, throwing an error.
auto txn = lmdb::txn::begin(env_, nullptr);
QMap<QString, mtx::responses::Notifications> notifs;
auto room_ids = getRoomIds(txn);
for (const auto &room_id : room_ids) {
auto roomNotifs = getTimelineMentionsForRoom(txn, room_id);
notifs[QString::fromStdString(room_id)] = roomNotifs;
}
txn.commit();
return notifs;
}
std::string std::string
Cache::previousBatchToken(const std::string &room_id) Cache::previousBatchToken(const std::string &room_id)
{ {
@ -3587,88 +3556,6 @@ Cache::clearTimeline(const std::string &room_id)
txn.commit(); txn.commit();
} }
mtx::responses::Notifications
Cache::getTimelineMentionsForRoom(lmdb::txn &txn, const std::string &room_id)
{
auto db = getMentionsDb(txn, room_id);
if (db.size(txn) == 0) {
return mtx::responses::Notifications{};
}
mtx::responses::Notifications notif;
std::string_view event_id, msg;
auto cursor = lmdb::cursor::open(txn, db);
while (cursor.get(event_id, msg, MDB_NEXT)) {
auto obj = nlohmann::json::parse(msg);
if (obj.count("event") == 0)
continue;
mtx::responses::Notification notification;
from_json(obj, notification);
notif.notifications.push_back(notification);
}
cursor.close();
std::reverse(notif.notifications.begin(), notif.notifications.end());
return notif;
}
//! Add all notifications containing a user mention to the db.
void
Cache::saveTimelineMentions(const mtx::responses::Notifications &res)
{
QMap<std::string, QList<mtx::responses::Notification>> notifsByRoom;
// Sort into room-specific 'buckets'
for (const auto &notif : res.notifications) {
nlohmann::json val = notif;
notifsByRoom[notif.room_id].push_back(notif);
}
auto txn = lmdb::txn::begin(env_);
// Insert the entire set of mentions for each room at a time.
QMap<std::string, QList<mtx::responses::Notification>>::const_iterator it =
notifsByRoom.constBegin();
auto end = notifsByRoom.constEnd();
while (it != end) {
nhlog::db()->debug("Storing notifications for " + it.key());
saveTimelineMentions(txn, it.key(), std::move(it.value()));
++it;
}
txn.commit();
}
void
Cache::saveTimelineMentions(lmdb::txn &txn,
const std::string &room_id,
const QList<mtx::responses::Notification> &res)
{
auto db = getMentionsDb(txn, room_id);
using namespace mtx::events;
using namespace mtx::events::state;
for (const auto &notif : res) {
const auto event_id = mtx::accessors::event_id(notif.event);
// double check that we have the correct room_id...
if (room_id.compare(notif.room_id) != 0) {
return;
}
nlohmann::json obj = notif;
db.put(txn, event_id, obj.dump());
}
}
void void
Cache::markSentNotification(const std::string &event_id) Cache::markSentNotification(const std::string &event_id)
{ {
@ -5299,12 +5186,6 @@ roomIds()
return instance_->roomIds(); return instance_->roomIds();
} }
QMap<QString, mtx::responses::Notifications>
getTimelineMentions()
{
return instance_->getTimelineMentions();
}
//! Retrieve all the user ids from a room. //! Retrieve all the user ids from a room.
std::vector<std::string> std::vector<std::string>
roomMembers(const std::string &room_id) roomMembers(const std::string &room_id)
@ -5405,13 +5286,6 @@ isNotificationSent(const std::string &event_id)
return instance_->isNotificationSent(event_id); return instance_->isNotificationSent(event_id);
} }
//! Add all notifications containing a user mention to the db.
void
saveTimelineMentions(const mtx::responses::Notifications &res)
{
instance_->saveTimelineMentions(res);
}
//! Remove old unused data. //! Remove old unused data.
void void
deleteOldMessages() deleteOldMessages()

View file

@ -118,9 +118,6 @@ setCurrentFormat();
bool bool
runMigrations(); runMigrations();
QMap<QString, mtx::responses::Notifications>
getTimelineMentions();
//! Retrieve all the user ids from a room. //! Retrieve all the user ids from a room.
std::vector<std::string> std::vector<std::string>
roomMembers(const std::string &room_id); roomMembers(const std::string &room_id);
@ -178,10 +175,6 @@ removeReadNotification(const std::string &event_id);
bool bool
isNotificationSent(const std::string &event_id); isNotificationSent(const std::string &event_id);
//! Add all notifications containing a user mention to the db.
void
saveTimelineMentions(const mtx::responses::Notifications &res);
//! Remove old unused data. //! Remove old unused data.
void void
deleteOldMessages(); deleteOldMessages();

View file

@ -138,7 +138,6 @@ public:
bool runMigrations(); bool runMigrations();
std::vector<QString> roomIds(); std::vector<QString> roomIds();
QMap<QString, mtx::responses::Notifications> getTimelineMentions();
//! Retrieve all the user ids from a room. //! Retrieve all the user ids from a room.
std::vector<std::string> roomMembers(const std::string &room_id); std::vector<std::string> roomMembers(const std::string &room_id);
@ -179,9 +178,6 @@ public:
//! Check if we have sent a desktop notification for the given event id. //! Check if we have sent a desktop notification for the given event id.
bool isNotificationSent(const std::string &event_id); bool isNotificationSent(const std::string &event_id);
//! Add all notifications containing a user mention to the db.
void saveTimelineMentions(const mtx::responses::Notifications &res);
//! retrieve events in timeline and related functions //! retrieve events in timeline and related functions
struct Messages struct Messages
{ {
@ -318,7 +314,6 @@ public:
signals: signals:
void newReadReceipts(const QString &room_id, const std::vector<QString> &event_ids); void newReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
void roomReadStatus(const std::map<QString, bool> &status); void roomReadStatus(const std::map<QString, bool> &status);
void removeNotification(const QString &room_id, const QString &event_id);
void userKeysUpdate(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery); void userKeysUpdate(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery);
void userKeysUpdateFinalize(const std::string &user_id); void userKeysUpdateFinalize(const std::string &user_id);
void verificationStatusChanged(const std::string &userid); void verificationStatusChanged(const std::string &userid);
@ -335,15 +330,6 @@ private:
lmdb::dbi &membersdb, lmdb::dbi &membersdb,
const mtx::responses::InvitedRoom &room); const mtx::responses::InvitedRoom &room);
//! Add a notification containing a user mention to the db.
void saveTimelineMentions(lmdb::txn &txn,
const std::string &room_id,
const QList<mtx::responses::Notification> &res);
//! Get timeline items that a user was mentions in for a given room
mtx::responses::Notifications
getTimelineMentionsForRoom(lmdb::txn &txn, const std::string &room_id);
QString getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb); QString getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
QString getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb); QString getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
QString getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb); QString getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
@ -642,11 +628,6 @@ private:
return lmdb::dbi::open(txn, std::string(room_id + "/members").c_str(), MDB_CREATE); return lmdb::dbi::open(txn, std::string(room_id + "/members").c_str(), MDB_CREATE);
} }
lmdb::dbi getMentionsDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(txn, std::string(room_id + "/mentions").c_str(), MDB_CREATE);
}
lmdb::dbi getUserKeysDb(lmdb::txn &txn) { return lmdb::dbi::open(txn, "user_key", MDB_CREATE); } lmdb::dbi getUserKeysDb(lmdb::txn &txn) { return lmdb::dbi::open(txn, "user_key", MDB_CREATE); }
lmdb::dbi getVerificationDb(lmdb::txn &txn) lmdb::dbi getVerificationDb(lmdb::txn &txn)

View file

@ -136,18 +136,6 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QObject *parent)
Qt::QueuedConnection); Qt::QueuedConnection);
connect(this, &ChatPage::leftRoom, this, &ChatPage::removeRoom); connect(this, &ChatPage::leftRoom, this, &ChatPage::removeRoom);
connect(this, &ChatPage::changeToRoom, this, &ChatPage::changeRoom, Qt::QueuedConnection); connect(this, &ChatPage::changeToRoom, this, &ChatPage::changeRoom, Qt::QueuedConnection);
connect(this, &ChatPage::notificationsRetrieved, this, &ChatPage::sendNotifications);
connect(this,
&ChatPage::highlightedNotifsRetrieved,
this,
[](const mtx::responses::Notifications &notif) {
try {
cache::saveTimelineMentions(notif);
} catch (const lmdb::error &e) {
nhlog::db()->error("failed to save mentions: {}", e.what());
}
});
connect(notificationsManager, connect(notificationsManager,
&NotificationsManager::notificationClicked, &NotificationsManager::notificationClicked,
this, this,
@ -208,18 +196,140 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QObject *parent)
// No need to check amounts for this section, as this function internally checks for // No need to check amounts for this section, as this function internally checks for
// duplicates. // duplicates.
if (notificationCount && userSettings_->hasNotifications()) if (notificationCount && userSettings_->hasNotifications())
http::client()->notifications( for (const auto &e : sync.account_data.events) {
5, if (auto newRules =
"", std::get_if<mtx::events::AccountDataEvent<mtx::pushrules::GlobalRuleset>>(&e))
"", pushrules =
[this](const mtx::responses::Notifications &res, mtx::http::RequestErr err) { std::make_unique<mtx::pushrules::PushRuleEvaluator>(newRules->content.global);
if (err) { }
nhlog::net()->warn("failed to retrieve notifications: {}", err); if (!pushrules) {
return; auto eventInDb = cache::client()->getAccountData(mtx::events::EventType::PushRules);
if (eventInDb) {
if (auto newRules =
std::get_if<mtx::events::AccountDataEvent<mtx::pushrules::GlobalRuleset>>(
&*eventInDb))
pushrules =
std::make_unique<mtx::pushrules::PushRuleEvaluator>(newRules->content.global);
}
}
if (pushrules) {
const auto local_user = utils::localUser().toStdString();
for (const auto &[room_id, room] : sync.rooms.join) {
// clear old notifications
for (const auto &e : room.ephemeral.events) {
if (auto receiptsEv =
std::get_if<mtx::events::EphemeralEvent<mtx::events::ephemeral::Receipt>>(
&e)) {
std::vector<QString> receipts;
for (const auto &[event_id, userReceipts] : receiptsEv->content.receipts) {
if (auto r = userReceipts.find(mtx::events::ephemeral::Receipt::Read);
r != userReceipts.end()) {
for (const auto &[user_id, receipt] : r->second.users) {
(void)receipt;
if (user_id == local_user) {
receipts.push_back(QString::fromStdString(event_id));
break;
}
}
}
if (auto r =
userReceipts.find(mtx::events::ephemeral::Receipt::ReadPrivate);
r != userReceipts.end()) {
for (const auto &[user_id, receipt] : r->second.users) {
(void)receipt;
if (user_id == local_user) {
receipts.push_back(QString::fromStdString(event_id));
break;
}
}
}
}
if (!receipts.empty())
notificationsManager->removeNotifications(
QString::fromStdString(room_id), receipts);
}
} }
emit notificationsRetrieved(std::move(res)); // calculate new notifications
if (!room.timeline.events.empty() &&
(room.unread_notifications.notification_count ||
room.unread_notifications.highlight_count)) {
auto roomModel =
view_manager_->rooms()->getRoomById(QString::fromStdString(room_id));
if (!roomModel) {
continue;
}
auto currentReadMarker =
cache::getEventIndex(room_id, cache::client()->getFullyReadEventId(room_id));
auto ctx = roomModel->pushrulesRoomContext();
for (const auto &event : room.timeline.events) {
mtx::events::collections::TimelineEvent te{event};
std::visit([room_id = room_id](auto &event_) { event_.room_id = room_id; },
te.data);
if (auto encryptedEvent =
std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
&event)) {
MegolmSessionIndex index(room_id, encryptedEvent->content);
auto result = olm::decryptEvent(index, *encryptedEvent);
if (result.event)
te.data = result.event.value();
}
auto actions = pushrules->evaluate(te, ctx);
if (std::find(actions.begin(),
actions.end(),
mtx::pushrules::actions::Action{
mtx::pushrules::actions::notify{}}) != actions.end()) {
auto event_id = mtx::accessors::event_id(event);
// skip already read events
if (currentReadMarker &&
currentReadMarker > cache::getEventIndex(room_id, event_id))
continue;
if (!cache::isNotificationSent(event_id)) {
// We should only send one notification per event.
cache::markSentNotification(event_id);
// Don't send a notification when the current room is opened.
if (isRoomActive(roomModel->roomId()))
continue;
if (userSettings_->hasDesktopNotifications()) {
auto info = cache::singleRoomInfo(room_id);
AvatarProvider::resolve(
roomModel->roomAvatarUrl(),
96,
this,
[this, te, room_id = room_id, actions](QPixmap image) {
notificationsManager->postNotification(
mtx::responses::Notification{
.actions = actions,
.event = te.data,
.read = false,
.profile_tag = "",
.room_id = room_id,
.ts = 0,
},
image.toImage());
}); });
}
}
}
}
}
}
}
}); });
connect( connect(
@ -394,12 +504,6 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
&Cache::newReadReceipts, &Cache::newReadReceipts,
view_manager_, view_manager_,
&TimelineViewManager::updateReadReceipts); &TimelineViewManager::updateReadReceipts);
connect(cache::client(),
&Cache::removeNotification,
notificationsManager,
&NotificationsManager::removeNotification);
} catch (const lmdb::error &e) { } catch (const lmdb::error &e) {
nhlog::db()->critical("failure during boot: {}", e.what()); nhlog::db()->critical("failure during boot: {}", e.what());
emit dropToLoginPageCb(tr("Failed to open database, logging out!")); emit dropToLoginPageCb(tr("Failed to open database, logging out!"));
@ -415,7 +519,6 @@ ChatPage::loadStateFromCache()
olm::client()->load(cache::restoreOlmAccount(), cache::client()->pickleSecret()); olm::client()->load(cache::restoreOlmAccount(), cache::client()->pickleSecret());
emit initializeEmptyViews(); emit initializeEmptyViews();
emit initializeMentions(cache::getTimelineMentions());
cache::calculateRoomReadStatus(); cache::calculateRoomReadStatus();
@ -464,46 +567,6 @@ ChatPage::removeRoom(const QString &room_id)
} }
} }
void
ChatPage::sendNotifications(const mtx::responses::Notifications &res)
{
for (const auto &item : res.notifications) {
const auto event_id = mtx::accessors::event_id(item.event);
try {
if (item.read) {
cache::removeReadNotification(event_id);
continue;
}
if (!cache::isNotificationSent(event_id)) {
const auto room_id = QString::fromStdString(item.room_id);
// We should only send one notification per event.
cache::markSentNotification(event_id);
// Don't send a notification when the current room is opened.
if (isRoomActive(room_id))
continue;
if (userSettings_->hasDesktopNotifications()) {
auto info = cache::singleRoomInfo(item.room_id);
AvatarProvider::resolve(QString::fromStdString(info.avatar_url),
96,
this,
[this, item](QPixmap image) {
notificationsManager->postNotification(
item, image.toImage());
});
}
}
} catch (const lmdb::error &e) {
nhlog::db()->warn("error while sending notification: {}", e.what());
}
}
}
void void
ChatPage::tryInitialSync() ChatPage::tryInitialSync()
{ {
@ -596,7 +659,6 @@ ChatPage::startInitialSync()
olm::handle_to_device_messages(res.to_device.events); olm::handle_to_device_messages(res.to_device.events);
emit initializeViews(std::move(res)); emit initializeViews(std::move(res));
emit initializeMentions(cache::getTimelineMentions());
cache::calculateRoomReadStatus(); cache::calculateRoomReadStatus();
} catch (const lmdb::error &e) { } catch (const lmdb::error &e) {

View file

@ -14,6 +14,7 @@
#include <mtx/events.hpp> #include <mtx/events.hpp>
#include <mtx/events/encrypted.hpp> #include <mtx/events/encrypted.hpp>
#include <mtx/events/member.hpp> #include <mtx/events/member.hpp>
#include <mtx/events/policy_rules.hpp>
#include <mtx/events/presence.hpp> #include <mtx/events/presence.hpp>
#include <mtx/secret_storage.hpp> #include <mtx/secret_storage.hpp>
@ -108,9 +109,6 @@ signals:
void connectionLost(); void connectionLost();
void connectionRestored(); void connectionRestored();
void notificationsRetrieved(const mtx::responses::Notifications &);
void highlightedNotifsRetrieved(const mtx::responses::Notifications &, const QPoint widgetPos);
void contentLoaded(); void contentLoaded();
void closing(); void closing();
void changeWindowTitle(const int); void changeWindowTitle(const int);
@ -135,7 +133,6 @@ signals:
void initializeViews(const mtx::responses::Sync &rooms); void initializeViews(const mtx::responses::Sync &rooms);
void initializeEmptyViews(); void initializeEmptyViews();
void initializeMentions(const QMap<QString, mtx::responses::Notifications> &notifs);
void syncUI(const mtx::responses::Sync &sync); void syncUI(const mtx::responses::Sync &sync);
void dropToLoginPageCb(const QString &msg); void dropToLoginPageCb(const QString &msg);
@ -206,9 +203,6 @@ private:
template<class Collection> template<class Collection>
Memberships getMemberships(const std::vector<Collection> &events) const; Memberships getMemberships(const std::vector<Collection> &events) const;
//! Send desktop notification for the received messages.
void sendNotifications(const mtx::responses::Notifications &);
template<typename T> template<typename T>
void connectCallMessage(); void connectCallMessage();
@ -222,6 +216,8 @@ private:
NotificationsManager *notificationsManager; NotificationsManager *notificationsManager;
CallManager *callManager_; CallManager *callManager_;
std::unique_ptr<mtx::pushrules::PushRuleEvaluator> pushrules;
}; };
template<class Collection> template<class Collection>

View file

@ -7,6 +7,7 @@
#include "Cache.h" #include "Cache.h"
#include "EventAccessors.h" #include "EventAccessors.h"
#include "Logging.h"
#include "Utils.h" #include "Utils.h"
QString QString
@ -33,3 +34,24 @@ NotificationsManager::getMessageTemplate(const mtx::responses::Notification &not
return QStringLiteral("%1: %2").arg(sender); return QStringLiteral("%1: %2").arg(sender);
} }
} }
void
NotificationsManager::removeNotifications(const QString &roomId,
const std::vector<QString> &eventIds)
{
std::string room_id = roomId.toStdString();
std::uint64_t markerPos = 0;
for (const auto &e : eventIds) {
markerPos = std::max(markerPos, cache::getEventIndex(room_id, e.toStdString()).value_or(0));
}
for (const auto &[roomId, eventId] : this->notificationIds) {
if (roomId != roomId)
continue;
auto idx = cache::getEventIndex(room_id, eventId.toStdString());
if (!idx || markerPos >= idx) {
removeNotification(roomId, eventId);
}
}
}

View file

@ -36,6 +36,8 @@ public:
void postNotification(const mtx::responses::Notification &notification, const QImage &icon); void postNotification(const mtx::responses::Notification &notification, const QImage &icon);
void removeNotification(const QString &roomId, const QString &eventId);
signals: signals:
void notificationClicked(const QString roomId, const QString eventId); void notificationClicked(const QString roomId, const QString eventId);
void sendNotificationReply(const QString roomId, const QString eventId, const QString body); void sendNotificationReply(const QString roomId, const QString eventId, const QString body);
@ -46,7 +48,7 @@ signals:
const QImage &icon); const QImage &icon);
public slots: public slots:
void removeNotification(const QString &roomId, const QString &eventId); void removeNotifications(const QString &roomId, const std::vector<QString> &eventId);
#if defined(NHEKO_DBUS_SYS) #if defined(NHEKO_DBUS_SYS)
public: public:
@ -62,9 +64,6 @@ private:
const QImage &icon); const QImage &icon);
void closeNotification(uint id); void closeNotification(uint id);
// notification ID to (room ID, event ID)
QMap<uint, roomEventId> notificationIds;
const bool hasMarkup_; const bool hasMarkup_;
const bool hasImages_; const bool hasImages_;
#endif #endif
@ -96,6 +95,10 @@ private slots:
private: private:
QString getMessageTemplate(const mtx::responses::Notification &notification); QString getMessageTemplate(const mtx::responses::Notification &notification);
// notification ID to (room ID, event ID)
// Only populated on Linux atm
QMap<uint, roomEventId> notificationIds;
}; };
#if defined(NHEKO_DBUS_SYS) #if defined(NHEKO_DBUS_SYS)

View file

@ -34,6 +34,8 @@ public:
void invalidate(); void invalidate();
const mtx::events::state::PowerLevels &powerlevelEvent() const { return pl; };
private: private:
QString roomId_; QString roomId_;
mtx::events::state::PowerLevels pl; mtx::events::state::PowerLevels pl;

View file

@ -2921,6 +2921,17 @@ TimelineModel::directChatOtherUserId() const
return {}; return {};
} }
mtx::pushrules::PushRuleEvaluator::RoomContext
TimelineModel::pushrulesRoomContext() const
{
return mtx::pushrules::PushRuleEvaluator::RoomContext{
.user_display_name =
cache::displayName(room_id_.toStdString(), http::client()->user_id().to_string()),
.member_count = cache::client()->memberCount(room_id_.toStdString()),
.power_levels = permissions_.powerlevelEvent(),
};
}
RoomSummary * RoomSummary *
TimelineModel::parentSpace() TimelineModel::parentSpace()
{ {

View file

@ -336,6 +336,8 @@ public:
bool isDirect() const { return roomMemberCount() <= 2; } bool isDirect() const { return roomMemberCount() <= 2; }
QString directChatOtherUserId() const; QString directChatOtherUserId() const;
mtx::pushrules::PushRuleEvaluator::RoomContext pushrulesRoomContext() const;
std::optional<mtx::events::collections::TimelineEvents> eventById(const QString &id) std::optional<mtx::events::collections::TimelineEvents> eventById(const QString &id)
{ {
auto e = events.get(id.toStdString(), ""); auto e = events.get(id.toStdString(), "");