summary refs log tree commit diff
diff options
context:
space:
mode:
authorKonstantinos Sideris <sideris.konstantin@gmail.com>2018-05-05 16:38:41 +0300
committerKonstantinos Sideris <sideris.konstantin@gmail.com>2018-05-05 16:38:41 +0300
commited9501023ae57e668a930e5d3accbb47ad3d7812 (patch)
tree11d0150f0bd3bae0013e67cbd3e184b778e9946b
parentAdd compile option for address sanitizers (diff)
downloadnheko-ed9501023ae57e668a930e5d3accbb47ad3d7812.tar.xz
Add support for retrieving the notification events (#33)
-rw-r--r--cmake/MatrixStructs.cmake2
-rw-r--r--include/Cache.h7
-rw-r--r--include/ChatPage.h2
-rw-r--r--include/MatrixClient.h2
-rw-r--r--include/Utils.h34
-rw-r--r--include/timeline/TimelineView.h3
-rw-r--r--src/Cache.cc45
-rw-r--r--src/ChatPage.cc38
-rw-r--r--src/MatrixClient.cc38
-rw-r--r--src/Utils.cc27
-rw-r--r--src/timeline/TimelineView.cc20
11 files changed, 192 insertions, 26 deletions
diff --git a/cmake/MatrixStructs.cmake b/cmake/MatrixStructs.cmake
index 02c0c9d0..cf8e4710 100644
--- a/cmake/MatrixStructs.cmake
+++ b/cmake/MatrixStructs.cmake
@@ -21,7 +21,7 @@ ExternalProject_Add(
   MatrixStructs
 
   GIT_REPOSITORY https://github.com/mujx/matrix-structs
-  GIT_TAG 690080daa3bc1984297c4d7103cde9ea07e2e0b7
+  GIT_TAG 55a1a5aad0ead3cc45475fc1aed1bf54a56e352c
 
   BUILD_IN_SOURCE 1
   SOURCE_DIR ${MATRIX_STRUCTS_ROOT}
diff --git a/include/Cache.h b/include/Cache.h
index 0829acf5..1fa6c430 100644
--- a/include/Cache.h
+++ b/include/Cache.h
@@ -233,6 +233,12 @@ public:
         std::vector<RoomSearchResult> searchRooms(const std::string &query,
                                                   std::uint8_t max_items = 5);
 
+        void markSentNotification(const std::string &event_id);
+        //! Removes an event from the sent notifications.
+        void removeReadNotification(const std::string &event_id);
+        //! Check if we have sent a desktop notification for the given event id.
+        bool isNotificationSent(const std::string &event_id);
+
 private:
         //! Save an invited room.
         void saveInvite(lmdb::txn &txn,
@@ -422,6 +428,7 @@ private:
         lmdb::dbi invitesDb_;
         lmdb::dbi mediaDb_;
         lmdb::dbi readReceiptsDb_;
+        lmdb::dbi notificationsDb_;
 
         QString localUserId_;
         QString cacheDirectory_;
diff --git a/include/ChatPage.h b/include/ChatPage.h
index 147ff6b2..f659163c 100644
--- a/include/ChatPage.h
+++ b/include/ChatPage.h
@@ -136,6 +136,8 @@ private:
 
         //! Update the room with the new notification count.
         void updateRoomNotificationCount(const QString &room_id, uint16_t notification_count);
+        //! Send desktop notification for the received messages.
+        void sendDesktopNotifications(const mtx::responses::Notifications &);
 
         QStringList generateTypingUsers(const QString &room_id,
                                         const std::vector<std::string> &typing_users);
diff --git a/include/MatrixClient.h b/include/MatrixClient.h
index 1be15e56..35f05c31 100644
--- a/include/MatrixClient.h
+++ b/include/MatrixClient.h
@@ -91,6 +91,7 @@ public:
         void redactEvent(const QString &room_id, const QString &event_id);
         void inviteUser(const QString &room_id, const QString &user);
         void createRoom(const mtx::requests::CreateRoom &request);
+        void getNotifications() noexcept;
 
         QUrl getHomeServer() { return server_; };
         int transactionId() { return txn_id_; };
@@ -178,6 +179,7 @@ signals:
         void redactionCompleted(const QString &room_id, const QString &event_id);
         void invalidToken();
         void syncError(const QString &error);
+        void notificationsRetrieved(const mtx::responses::Notifications &notifications);
 
 private:
         QNetworkReply *makeUploadRequest(QSharedPointer<QIODevice> iodev);
diff --git a/include/Utils.h b/include/Utils.h
index c9dc460a..6fea4962 100644
--- a/include/Utils.h
+++ b/include/Utils.h
@@ -32,6 +32,9 @@ firstChar(const QString &input);
 QString
 humanReadableFileSize(uint64_t bytes);
 
+QString
+event_body(const mtx::events::collections::TimelineEvents &event);
+
 //! Match widgets/events with a description message.
 template<class T>
 QString
@@ -131,6 +134,37 @@ erase_if(ContainerT &items, const PredicateT &predicate)
         }
 }
 
+inline mtx::events::EventType
+event_type(const mtx::events::collections::TimelineEvents &event)
+{
+        return mpark::visit([](auto msg) { return msg.type; }, event);
+}
+
+inline std::string
+event_id(const mtx::events::collections::TimelineEvents &event)
+{
+        return mpark::visit([](auto msg) { return msg.event_id; }, event);
+}
+
+inline QString
+eventId(const mtx::events::collections::TimelineEvents &event)
+{
+        return QString::fromStdString(event_id(event));
+}
+
+inline QString
+event_sender(const mtx::events::collections::TimelineEvents &event)
+{
+        return mpark::visit([](auto msg) { return QString::fromStdString(msg.sender); }, event);
+}
+
+template<class T>
+QString
+message_body(const mtx::events::collections::TimelineEvents &event)
+{
+        return QString::fromStdString(mpark::get<T>(event).content.body);
+}
+
 //! Calculate the Levenshtein distance between two strings with character skipping.
 int
 levenshtein_distance(const std::string &s1, const std::string &s2);
diff --git a/include/timeline/TimelineView.h b/include/timeline/TimelineView.h
index ab4fbd47..02e2872a 100644
--- a/include/timeline/TimelineView.h
+++ b/include/timeline/TimelineView.h
@@ -211,9 +211,6 @@ private:
         bool isScrollbarActivated() { return scroll_area_->verticalScrollBar()->value() != 0; }
         //! Retrieve the event id of the last item.
         QString getLastEventId() const;
-        QString getEventSender(const mtx::events::collections::TimelineEvents &event) const;
-        mtx::events::EventType getEventType(
-          const mtx::events::collections::TimelineEvents &event) const;
 
         template<class Event, class Widget>
         TimelineItem *processMessageEvent(const Event &event, TimelineDirection direction);
diff --git a/src/Cache.cc b/src/Cache.cc
index 92c86322..8b00a828 100644
--- a/src/Cache.cc
+++ b/src/Cache.cc
@@ -48,6 +48,7 @@ static constexpr const char *MEDIA_DB = "media";
 static constexpr const char *SYNC_STATE_DB = "sync_state";
 //! Read receipts per room/event.
 static constexpr const char *READ_RECEIPTS_DB = "read_receipts";
+static constexpr const char *NOTIFICATIONS_DB = "sent_notifications";
 
 using CachedReceipts = std::multimap<uint64_t, std::string, std::greater<uint64_t>>;
 using Receipts       = std::map<std::string, std::map<std::string, uint64_t>>;
@@ -60,6 +61,7 @@ Cache::Cache(const QString &userId, QObject *parent)
   , invitesDb_{0}
   , mediaDb_{0}
   , readReceiptsDb_{0}
+  , notificationsDb_{0}
   , localUserId_{userId}
 {}
 
@@ -112,12 +114,13 @@ Cache::setup()
                 env_.open(statePath.toStdString().c_str());
         }
 
-        auto txn        = lmdb::txn::begin(env_);
-        syncStateDb_    = lmdb::dbi::open(txn, SYNC_STATE_DB, MDB_CREATE);
-        roomsDb_        = lmdb::dbi::open(txn, ROOMS_DB, MDB_CREATE);
-        invitesDb_      = lmdb::dbi::open(txn, INVITES_DB, MDB_CREATE);
-        mediaDb_        = lmdb::dbi::open(txn, MEDIA_DB, MDB_CREATE);
-        readReceiptsDb_ = lmdb::dbi::open(txn, READ_RECEIPTS_DB, MDB_CREATE);
+        auto txn         = lmdb::txn::begin(env_);
+        syncStateDb_     = lmdb::dbi::open(txn, SYNC_STATE_DB, MDB_CREATE);
+        roomsDb_         = lmdb::dbi::open(txn, ROOMS_DB, MDB_CREATE);
+        invitesDb_       = lmdb::dbi::open(txn, INVITES_DB, MDB_CREATE);
+        mediaDb_         = lmdb::dbi::open(txn, MEDIA_DB, MDB_CREATE);
+        readReceiptsDb_  = lmdb::dbi::open(txn, READ_RECEIPTS_DB, MDB_CREATE);
+        notificationsDb_ = lmdb::dbi::open(txn, NOTIFICATIONS_DB, MDB_CREATE);
         txn.commit();
 
         qRegisterMetaType<RoomInfo>();
@@ -1087,6 +1090,36 @@ Cache::getMembers(const std::string &room_id, std::size_t startIndex, std::size_
         return members;
 }
 
+void
+Cache::markSentNotification(const std::string &event_id)
+{
+        auto txn = lmdb::txn::begin(env_);
+        lmdb::dbi_put(txn, notificationsDb_, lmdb::val(event_id), lmdb::val(std::string("")));
+        txn.commit();
+}
+
+void
+Cache::removeReadNotification(const std::string &event_id)
+{
+        auto txn = lmdb::txn::begin(env_);
+
+        lmdb::dbi_del(txn, notificationsDb_, lmdb::val(event_id), nullptr);
+
+        txn.commit();
+}
+
+bool
+Cache::isNotificationSent(const std::string &event_id)
+{
+        auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
+
+        lmdb::val value;
+        bool res = lmdb::dbi_get(txn, notificationsDb_, lmdb::val(event_id), value);
+        txn.commit();
+
+        return res;
+}
+
 QHash<QString, QString> Cache::DisplayNames;
 QHash<QString, QString> Cache::AvatarUrls;
 
diff --git a/src/ChatPage.cc b/src/ChatPage.cc
index ee338c2d..4750e67a 100644
--- a/src/ChatPage.cc
+++ b/src/ChatPage.cc
@@ -36,6 +36,7 @@
 #include "TypingDisplay.h"
 #include "UserInfoWidget.h"
 #include "UserSettingsPage.h"
+#include "Utils.h"
 
 #include "dialogs/ReadReceipts.h"
 #include "timeline/TimelineViewManager.h"
@@ -339,6 +340,10 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client,
         connect(client_.data(), &MatrixClient::redactionFailed, this, [this](const QString &error) {
                 emit showNotification(QString("Message redaction failed: %1").arg(error));
         });
+        connect(client_.data(),
+                &MatrixClient::notificationsRetrieved,
+                this,
+                &ChatPage::sendDesktopNotifications);
 
         showContentTimer_ = new QTimer(this);
         showContentTimer_->setSingleShot(true);
@@ -420,13 +425,20 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client,
                 view_manager_->initialize(rooms);
                 removeLeftRooms(rooms.leave);
 
+                bool hasNotifications = false;
                 for (const auto &room : rooms.join) {
                         auto room_id = QString::fromStdString(room.first);
 
                         updateTypingUsers(room_id, room.second.ephemeral.typing);
                         updateRoomNotificationCount(
                           room_id, room.second.unread_notifications.notification_count);
+
+                        if (room.second.unread_notifications.notification_count > 0)
+                                hasNotifications = true;
                 }
+
+                if (hasNotifications)
+                        client_->getNotifications();
         });
         connect(this, &ChatPage::syncRoomlist, room_list_, &RoomList::sync);
 
@@ -838,3 +850,29 @@ ChatPage::updateRoomNotificationCount(const QString &room_id, uint16_t notificat
 {
         room_list_->updateUnreadMessageCount(room_id, notification_count);
 }
+
+void
+ChatPage::sendDesktopNotifications(const mtx::responses::Notifications &res)
+{
+        for (const auto &item : res.notifications) {
+                const auto event_id = utils::event_id(item.event);
+
+                try {
+                        if (item.read) {
+                                cache_->removeReadNotification(event_id);
+                                continue;
+                        }
+
+                        if (!cache_->isNotificationSent(event_id)) {
+                                // TODO: send desktop notification
+                                // qDebug() << "sender" << utils::event_sender(item.event);
+                                // qDebug() << "body" << utils::event_body(item.event);
+
+                                // We should only sent one notification per event.
+                                // cache_->markSentNotification(event_id);
+                        }
+                } catch (const lmdb::error &e) {
+                        qWarning() << e.what();
+                }
+        }
+}
diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc
index de930fc3..54756c7c 100644
--- a/src/MatrixClient.cc
+++ b/src/MatrixClient.cc
@@ -1310,3 +1310,41 @@ MatrixClient::redactEvent(const QString &room_id, const QString &event_id)
                 }
         });
 }
+
+void
+MatrixClient::getNotifications() noexcept
+{
+        QUrlQuery query;
+        query.addQueryItem("limit", "5");
+
+        QUrl endpoint(server_);
+        endpoint.setQuery(query);
+        endpoint.setPath(clientApiUrl_ + "/notifications");
+
+        QNetworkRequest request(QString(endpoint.toEncoded()));
+        setupAuth(request);
+
+        auto reply = get(request);
+        connect(reply, &QNetworkReply::finished, this, [reply, this]() {
+                reply->deleteLater();
+
+                int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+                auto data  = reply->readAll();
+
+                if (status == 0 || status >= 400) {
+                        try {
+                                mtx::errors::Error res = nlohmann::json::parse(data);
+                                std::cout << nlohmann::json::parse(data).dump(2) << '\n';
+                                // TODO: Response with an error signal
+                                return;
+                        } catch (const std::exception &) {
+                        }
+                }
+
+                try {
+                        emit notificationsRetrieved(nlohmann::json::parse(data));
+                } catch (const std::exception &e) {
+                        qWarning() << "failed to parse /notifications response" << e.what();
+                }
+        });
+}
diff --git a/src/Utils.cc b/src/Utils.cc
index d9b06b52..14620145 100644
--- a/src/Utils.cc
+++ b/src/Utils.cc
@@ -111,3 +111,30 @@ utils::levenshtein_distance(const std::string &s1, const std::string &s2)
 
         return *std::min_element(row1.begin(), row1.end());
 }
+
+QString
+utils::event_body(const mtx::events::collections::TimelineEvents &event)
+{
+        using namespace mtx::events;
+        using namespace mtx::events::msg;
+
+        if (mpark::holds_alternative<RoomEvent<Audio>>(event)) {
+                return message_body<RoomEvent<Audio>>(event);
+        } else if (mpark::holds_alternative<RoomEvent<Emote>>(event)) {
+                return message_body<RoomEvent<Emote>>(event);
+        } else if (mpark::holds_alternative<RoomEvent<File>>(event)) {
+                return message_body<RoomEvent<File>>(event);
+        } else if (mpark::holds_alternative<RoomEvent<Image>>(event)) {
+                return message_body<RoomEvent<Image>>(event);
+        } else if (mpark::holds_alternative<RoomEvent<Notice>>(event)) {
+                return message_body<RoomEvent<Notice>>(event);
+        } else if (mpark::holds_alternative<Sticker>(event)) {
+                return message_body<Sticker>(event);
+        } else if (mpark::holds_alternative<RoomEvent<Text>>(event)) {
+                return message_body<RoomEvent<Text>>(event);
+        } else if (mpark::holds_alternative<RoomEvent<Video>>(event)) {
+                return message_body<RoomEvent<Video>>(event);
+        }
+
+        return QString();
+}
diff --git a/src/timeline/TimelineView.cc b/src/timeline/TimelineView.cc
index 8781f90e..5b433674 100644
--- a/src/timeline/TimelineView.cc
+++ b/src/timeline/TimelineView.cc
@@ -727,12 +727,6 @@ TimelineView::event(QEvent *event)
         return QWidget::event(event);
 }
 
-QString
-TimelineView::getEventSender(const mtx::events::collections::TimelineEvents &event) const
-{
-        return mpark::visit([](auto msg) { return QString::fromStdString(msg.sender); }, event);
-}
-
 void
 TimelineView::toggleScrollDownButton()
 {
@@ -826,8 +820,8 @@ TimelineView::relativeWidget(TimelineItem *item, int dt) const
 TimelineEvent
 TimelineView::findFirstViewableEvent(const std::vector<TimelineEvent> &events)
 {
-        auto it = std::find_if(events.begin(), events.end(), [this](const auto &event) {
-                return mtx::events::EventType::RoomMessage == getEventType(event);
+        auto it = std::find_if(events.begin(), events.end(), [](const auto &event) {
+                return mtx::events::EventType::RoomMessage == utils::event_type(event);
         });
 
         return (it == std::end(events)) ? events.front() : *it;
@@ -836,19 +830,13 @@ TimelineView::findFirstViewableEvent(const std::vector<TimelineEvent> &events)
 TimelineEvent
 TimelineView::findLastViewableEvent(const std::vector<TimelineEvent> &events)
 {
-        auto it = std::find_if(events.rbegin(), events.rend(), [this](const auto &event) {
-                return mtx::events::EventType::RoomMessage == getEventType(event);
+        auto it = std::find_if(events.rbegin(), events.rend(), [](const auto &event) {
+                return mtx::events::EventType::RoomMessage == utils::event_type(event);
         });
 
         return (it == std::rend(events)) ? events.back() : *it;
 }
 
-inline mtx::events::EventType
-TimelineView::getEventType(const mtx::events::collections::TimelineEvents &event) const
-{
-        return mpark::visit([](auto msg) { return msg.type; }, event);
-}
-
 void
 TimelineView::saveMessageInfo(const QString &sender,
                               uint64_t origin_server_ts,