From 80b48b270ed66e3557b98d9fc8e82ad868bcde80 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Thu, 20 May 2021 22:33:32 +0200 Subject: Add notifications tab to matrix --- src/plugins/Matrix.cpp | 212 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 181 insertions(+), 31 deletions(-) (limited to 'src/plugins/Matrix.cpp') diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index e015ada..7992772 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -348,8 +348,8 @@ namespace QuickMedia { //body_item.reset(); } - MatrixQuickMedia::MatrixQuickMedia(Program *program, Matrix *matrix, MatrixRoomsPage *rooms_page, MatrixRoomTagsPage *room_tags_page, MatrixInvitesPage *invites_page) : - program(program), matrix(matrix), chat_page(nullptr), rooms_page(rooms_page), room_tags_page(room_tags_page), invites_page(invites_page) + MatrixQuickMedia::MatrixQuickMedia(Program *program, Matrix *matrix, MatrixRoomsPage *rooms_page, MatrixRoomTagsPage *room_tags_page, MatrixInvitesPage *invites_page, MatrixNotificationsPage *notifications_page) : + program(program), matrix(matrix), chat_page(nullptr), rooms_page(rooms_page), room_tags_page(room_tags_page), invites_page(invites_page), notifications_page(notifications_page) { rooms_page->matrix_delegate = this; room_tags_page->matrix_delegate = this; @@ -405,7 +405,17 @@ namespace QuickMedia { if(message->notification_mentions_me) { // TODO: What if the message or username begins with "-"? also make the notification image be the avatar of the user if((!is_window_focused || room != current_room) && message->related_event_type != RelatedEventType::EDIT && message->related_event_type != RelatedEventType::REDACTION) { - show_notification("QuickMedia matrix - " + extract_first_line_remove_newline_elipses(matrix->message_get_author_displayname(message.get()), AUTHOR_MAX_LENGTH) + " (" + room->get_name() + ")", message->body); + std::string body = remove_reply_formatting(message->body); + show_notification("QuickMedia matrix - " + extract_first_line_remove_newline_elipses(matrix->message_get_author_displayname(message.get()), AUTHOR_MAX_LENGTH) + " (" + room->get_name() + ")", body); + + MatrixNotification notification; + notification.room = room; + notification.event_id = message->event_id; + notification.sender_user_id = message->user->user_id; + notification.body = std::move(body); + notification.timestamp = message->timestamp; + notification.read = false; + notifications_page->add_notification(std::move(notification)); } } } @@ -433,8 +443,8 @@ namespace QuickMedia { invites_page->remove_body_item_by_room_id(room_id); } - void MatrixQuickMedia::add_unread_notification(RoomData *room, std::string, std::string sender, std::string body) { - show_notification("QuickMedia matrix - " + sender + " (" + room->get_name() + ")", body); + void MatrixQuickMedia::add_unread_notification(MatrixNotification notification) { + show_notification("QuickMedia matrix - " + notification.sender_user_id + " (" + notification.room->get_name() + ")", notification.body); } static UsersByRoom::iterator find_user_data_by_id(UsersByRoom &users_by_room, const MatrixEventUserInfo &user_info) { @@ -481,6 +491,10 @@ namespace QuickMedia { } } + void MatrixQuickMedia::set_room_as_read(RoomData *room) { + notifications_page->set_room_as_read(room); + } + static void sort_room_body_items(std::vector> &room_body_items) { std::sort(room_body_items.begin(), room_body_items.end(), [](const std::shared_ptr &body_item1, const std::shared_ptr &body_item2) { RoomData *room1 = static_cast(body_item1->userdata); @@ -695,6 +709,10 @@ namespace QuickMedia { current_chat_page = chat_page; } + void MatrixRoomsPage::set_room_as_read(RoomData *room) { + matrix_delegate->set_room_as_read(room); + } + void MatrixRoomsPage::clear_data() { body->clear_items(); if(current_chat_page) @@ -966,6 +984,10 @@ namespace QuickMedia { return users_body ? users_body->items.size() : 0; } + void MatrixChatPage::set_room_as_read(RoomData *room) { + rooms_page->set_room_as_read(room); + } + PluginResult MatrixRoomDirectoryPage::submit(const std::string &title, const std::string&, std::vector &result_tabs) { std::string server_name = title; @@ -1011,6 +1033,77 @@ namespace QuickMedia { return PluginResult::OK; } + class NotificationsExtraData : public BodyItemExtra { + public: + RoomData *room; + bool read; + }; + + static std::shared_ptr notification_to_body_item(Body *body, const MatrixNotification ¬ification) { + auto body_item = BodyItem::create(""); + body_item->set_author(notification.room->get_name()); + body_item->set_description(notification.sender_user_id + ":\n" + notification.body); + body_item->set_timestamp(notification.timestamp); + body_item->url = notification.event_id; + + if(!notification.read) { + body_item->set_author_color(sf::Color(255, 100, 100)); + body_item->set_description_color(sf::Color(255, 100, 100)); + } + + body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE; + body_item->thumbnail_size = sf::Vector2i(32, 32); + body_item->thumbnail_url = notification.room->get_avatar_url(); + + auto extra_data = std::make_shared(); + extra_data->room = notification.room; + extra_data->read = notification.read; + body_item->extra = std::move(extra_data); + + body->apply_search_filter_for_item(body_item.get()); + return body_item; + } + + MatrixNotificationsPage::MatrixNotificationsPage(Program *program, Matrix *matrix, Body *notifications_body) : + LazyFetchPage(program), matrix(matrix), notifications_body(notifications_body) {} + + PluginResult MatrixNotificationsPage::get_page(const std::string&, int, BodyItems &result_items) { + return matrix->get_previous_notifications([this, &result_items](const MatrixNotification ¬ification) { + result_items.push_back(notification_to_body_item(notifications_body, notification)); + }); + } + + PluginResult MatrixNotificationsPage::lazy_fetch(BodyItems &result_items) { + matrix->get_cached_notifications([this, &result_items](const MatrixNotification ¬ification) { + result_items.push_back(notification_to_body_item(notifications_body, notification)); + }); + return PluginResult::OK; + } + + bool MatrixNotificationsPage::is_ready() { + return matrix->has_finished_fetching_notifications(); + } + + void MatrixNotificationsPage::add_notification(MatrixNotification notification) { + //int prev_selected_item = notifications_body->get_selected_item(); + //notifications_body->items.push_back(notification_to_body_item(notifications_body, notification)); + //notifications_body->set_selected_item(prev_selected_item - 1); + int prev_selected_item = notifications_body->get_selected_item(); + notifications_body->items.insert(notifications_body->items.begin(), notification_to_body_item(notifications_body, notification)); + notifications_body->set_selected_item(prev_selected_item + 1, false); + } + + void MatrixNotificationsPage::set_room_as_read(RoomData *room) { + for(auto &body_item : notifications_body->items) { + NotificationsExtraData *extra_data = static_cast(body_item->extra.get()); + if(!extra_data->read && extra_data->room == room) { + extra_data->read = true; + body_item->set_author_color(sf::Color::White); + body_item->set_description_color(sf::Color::White); + } + } + } + static std::array sync_fail_error_codes = { "M_FORBIDDEN", "M_UNKNOWN_TOKEN", @@ -1184,24 +1277,17 @@ namespace QuickMedia { if(initial_sync) { notification_thread = std::thread([this]() { - std::vector additional_args = { - { "-H", "Authorization: Bearer " + access_token } - }; - - // TODO: Instead of guessing notification limit with 100, accumulate rooms unread_notifications count and use that as the limit - // (and take into account that notification response may have notifications after call to sync above). - char url[512]; - snprintf(url, sizeof(url), "%s/_matrix/client/r0/notifications?limit=100&only=highlight", homeserver.c_str()); - - rapidjson::Document json_root; - DownloadResult download_result = download_json(json_root, url, std::move(additional_args), true); - if(download_result != DownloadResult::OK || !json_root.IsObject()) { - fprintf(stderr, "Fetching notifications failed!\n"); - return; - } + get_previous_notifications([this](const MatrixNotification ¬ification) { + if(notification.read) + return; + + MatrixDelegate *delegate = this->delegate; + ui_thread_tasks.push([delegate, notification] { + delegate->add_unread_notification(std::move(notification)); + }); + }); - const rapidjson::Value ¬ification_json = GetMember(json_root, "notifications"); - parse_notifications(notification_json); + finished_fetching_notifications = true; { std::vector additional_args = { @@ -1288,10 +1374,12 @@ namespace QuickMedia { delegate = nullptr; sync_failed = false; sync_fail_reason.clear(); - set_next_batch(""); + next_batch.clear(); + next_notifications_token.clear(); invites.clear(); filter_cached.reset(); my_events_transaction_ids.clear(); + finished_fetching_notifications = false; } bool Matrix::is_initial_sync_finished() const { @@ -1307,6 +1395,10 @@ namespace QuickMedia { } } + bool Matrix::has_finished_fetching_notifications() const { + return finished_fetching_notifications; + } + void Matrix::get_room_sync_data(RoomData *room, SyncData &sync_data) { room->acquire_room_lock(); auto &room_messages = room->get_messages_thread_unsafe(); @@ -1356,6 +1448,45 @@ namespace QuickMedia { return PluginResult::OK; } + PluginResult Matrix::get_previous_notifications(std::function callback_func) { + std::vector additional_args = { + { "-H", "Authorization: Bearer " + access_token } + }; + + std::string from = get_next_notifications_token(); + + // TODO: Instead of guessing notification limit with 100, accumulate rooms unread_notifications count and use that as the limit + // (and take into account that notification response may have notifications after call to sync above). + char url[512]; + if(from.empty()) + snprintf(url, sizeof(url), "%s/_matrix/client/r0/notifications?limit=100&only=highlight", homeserver.c_str()); + else + snprintf(url, sizeof(url), "%s/_matrix/client/r0/notifications?limit=100&only=highlight&from=%s", homeserver.c_str(), from.c_str()); + + rapidjson::Document json_root; + DownloadResult download_result = download_json(json_root, url, std::move(additional_args), true); + if(download_result != DownloadResult::OK || !json_root.IsObject()) { + fprintf(stderr, "Fetching notifications failed!\n"); + return PluginResult::ERR; + } + + const rapidjson::Value ¬ification_json = GetMember(json_root, "notifications"); + parse_notifications(notification_json, std::move(callback_func)); + + const rapidjson::Value &next_token_json = GetMember(json_root, "next_token"); + if(next_token_json.IsString()) + set_next_notifications_token(next_token_json.GetString()); + + return PluginResult::OK; + } + + void Matrix::get_cached_notifications(std::function callback_func) { + std::lock_guard lock(notifications_mutex); + for(const auto ¬ification : notifications) { + callback_func(notification); + } + } + PluginResult Matrix::parse_sync_response(const rapidjson::Document &root, bool is_additional_messages_sync, bool initial_sync) { if(!root.IsObject()) return PluginResult::ERR; @@ -1371,22 +1502,28 @@ namespace QuickMedia { return PluginResult::OK; } - PluginResult Matrix::parse_notifications(const rapidjson::Value ¬ifications_json) { + PluginResult Matrix::parse_notifications(const rapidjson::Value ¬ifications_json, std::function callback_func) { if(!notifications_json.IsArray()) return PluginResult::ERR; + std::lock_guard lock(notifications_mutex); for(const rapidjson::Value ¬ification_json : notifications_json.GetArray()) { if(!notification_json.IsObject()) continue; const rapidjson::Value &read_json = GetMember(notification_json, "read"); - if(!read_json.IsBool() || read_json.GetBool()) + if(!read_json.IsBool()) continue; const rapidjson::Value &room_id_json = GetMember(notification_json, "room_id"); if(!room_id_json.IsString()) continue; + time_t timestamp = 0; + const rapidjson::Value &ts_json = GetMember(notification_json, "ts"); + if(ts_json.IsInt64()) + timestamp = ts_json.GetInt64(); + const rapidjson::Value &event_json = GetMember(notification_json, "event"); if(!event_json.IsObject()) continue; @@ -1414,12 +1551,15 @@ namespace QuickMedia { continue; } - std::string event_id(event_id_json.GetString(), event_id_json.GetStringLength()); - std::string sender(sender_json.GetString(), sender_json.GetStringLength()); - std::string body(body_json.GetString(), body_json.GetStringLength()); - ui_thread_tasks.push([this, room, event_id{std::move(event_id)}, sender{std::move(sender)}, body{std::move(body)}] { - delegate->add_unread_notification(room, std::move(event_id), std::move(sender), std::move(body)); - }); + MatrixNotification notification; + notification.room = room; + notification.event_id.assign(event_id_json.GetString(), event_id_json.GetStringLength()); + notification.sender_user_id.assign(sender_json.GetString(), sender_json.GetStringLength()); + notification.body = remove_reply_formatting(body_json.GetString()); + notification.timestamp = timestamp; + notification.read = read_json.GetBool(); + callback_func(notification); + notifications.push_back(std::move(notification)); } return PluginResult::OK; } @@ -3967,6 +4107,16 @@ namespace QuickMedia { return next_batch; } + void Matrix::set_next_notifications_token(std::string new_next_token) { + std::lock_guard lock(next_batch_mutex); + next_notifications_token = std::move(new_next_token); + } + + std::string Matrix::get_next_notifications_token() { + std::lock_guard lock(next_batch_mutex); + return next_notifications_token; + } + void Matrix::clear_sync_cache_for_new_sync() { std::lock_guard room_data_lock(room_data_mutex); std::lock_guard invites_lock(invite_mutex); -- cgit v1.2.3