diff options
-rw-r--r-- | TODO | 2 | ||||
-rw-r--r-- | include/Body.hpp | 3 | ||||
-rw-r--r-- | plugins/Matrix.hpp | 85 | ||||
-rw-r--r-- | plugins/Page.hpp | 5 | ||||
-rw-r--r-- | src/Body.cpp | 12 | ||||
-rw-r--r-- | src/QuickMedia.cpp | 155 | ||||
-rw-r--r-- | src/plugins/Matrix.cpp | 486 |
7 files changed, 270 insertions, 478 deletions
@@ -66,7 +66,7 @@ Pressing enter on a pinned message should go to the message in the messages tab. Display file list for nyaa. Remove reply formatting for NOTICE in matrix as well. Implement our own encryption for matrix. This is also needed to make forwarded message work. Pantalaimon ignores them! -Modify matrix sync to download and parse json but not handle it, and then add a function to handle the json. This would allow us to remove all the mutex code if we would call that new method from the main thread (similar to chromium multithreading). +Modify matrix sync to download and parse json but not handle it, and then add a function to handle the json. This would allow us to remove all the mutex code if we would call that new method from the main thread. Fetch replies/pinned message using multiple threads. Show in room tags list when there is a message in any of the rooms in the tag. Show images while they download by showing them as scanlines starting from the top. Needed for slow websites such as 4chan. diff --git a/include/Body.hpp b/include/Body.hpp index 9798992..8e571f1 100644 --- a/include/Body.hpp +++ b/include/Body.hpp @@ -231,7 +231,7 @@ namespace QuickMedia { float get_spacing_y() const; // TODO: Highlight the part of the text that matches the search. - void filter_search_fuzzy(const std::string &text); + void filter_search_fuzzy(const std::string &text, bool select_first_if_empty = true); bool no_items_visible() const; @@ -331,5 +331,6 @@ namespace QuickMedia { // TODO: Instead of using this, add functions for modifying |items| and apply the filter on those new items bool items_dirty = false; std::string current_filter; + bool using_filter = false; }; }
\ No newline at end of file diff --git a/plugins/Matrix.hpp b/plugins/Matrix.hpp index 0c90587..9d6f0b6 100644 --- a/plugins/Matrix.hpp +++ b/plugins/Matrix.hpp @@ -252,6 +252,7 @@ namespace QuickMedia { BANNED }; + // All of methods in this class are called in the main (ui) thread class MatrixDelegate { public: virtual ~MatrixDelegate() = default; @@ -270,8 +271,6 @@ namespace QuickMedia { virtual void add_unread_notification(RoomData *room, std::string event_id, std::string sender, std::string body) = 0; - virtual void update(MatrixPageType page_type, Body *chat_body, bool messages_tab_visible) { (void)page_type; } - virtual void clear_data() = 0; }; @@ -296,53 +295,31 @@ namespace QuickMedia { void add_unread_notification(RoomData *room, std::string event_id, std::string sender, std::string body) override; - void update(MatrixPageType page_type, Body *chat_body, bool messages_tab_visible) override; - void clear_data() override; Program *program; Matrix *matrix; + MatrixChatPage *chat_page; MatrixRoomsPage *rooms_page; MatrixRoomTagsPage *room_tags_page; MatrixInvitesPage *invites_page; private: - void update_room_description(RoomData *room, Messages &new_messages, bool is_initial_sync, bool sync_is_cache, Body *chat_body, bool messages_tab_visible); - void update_pending_room_messages(MatrixPageType page_type, Body *chat_body, bool messages_tab_visible); + void update_room_description(RoomData *room, const Messages &new_messages, bool is_initial_sync, bool sync_is_cache); private: - struct RoomMessagesData { - Messages messages; - bool is_initial_sync; - bool sync_is_cache; - MessageDirection message_dir; - }; - - struct Notification { - std::string event_id; - std::string sender; - std::string body; - }; - std::map<RoomData*, std::shared_ptr<BodyItem>> room_body_item_by_room; - std::mutex room_body_items_mutex; - std::map<RoomData*, RoomMessagesData> pending_room_messages; - std::mutex pending_room_messages_mutex; - - std::unordered_map<RoomData*, std::vector<Notification>> unread_notifications; std::map<RoomData*, std::shared_ptr<Message>> last_message_by_room; }; class MatrixRoomsPage : public Page { public: - MatrixRoomsPage(Program *program, Body *body, std::string title, MatrixRoomTagsPage *room_tags_page, SearchBar *search_bar); + MatrixRoomsPage(Program *program, Body *body, std::string title, MatrixRoomTagsPage *room_tags_page); ~MatrixRoomsPage() override; const char* get_title() const override { return title.c_str(); } PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override; + bool submit_is_async() override { return false; } bool clear_search_after_submit() override { return true; } - void on_navigate_to_page(Body *body) override; - - void update() override; void add_body_item(std::shared_ptr<BodyItem> body_item); void move_room_to_top(RoomData *room); @@ -351,31 +328,23 @@ namespace QuickMedia { void set_current_chat_page(MatrixChatPage *chat_page); void clear_data(); - void sort_rooms(); MatrixQuickMedia *matrix_delegate = nullptr; - bool filter_on_update = false; private: - std::mutex mutex; - std::vector<std::shared_ptr<BodyItem>> room_body_items; - std::vector<std::string> pending_remove_body_items; Body *body = nullptr; std::string title; MatrixRoomTagsPage *room_tags_page = nullptr; - SearchBar *search_bar = nullptr; MatrixChatPage *current_chat_page = nullptr; - bool clear_data_on_update = false; - bool sort_on_update = false; }; class MatrixRoomTagsPage : public Page { public: - MatrixRoomTagsPage(Program *program, Body *body, SearchBar *search_bar) : Page(program), body(body), search_bar(search_bar) {} + MatrixRoomTagsPage(Program *program, Body *body) : Page(program), body(body) {} const char* get_title() const override { return "Tags"; } PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override; + bool submit_is_async() override { return false; } bool clear_search_after_submit() override { return true; } - void update() override; void add_room_body_item_to_tag(std::shared_ptr<BodyItem> body_item, const std::string &tag); void remove_room_body_item_from_tag(std::shared_ptr<BodyItem> body_item, const std::string &tag); @@ -385,50 +354,37 @@ namespace QuickMedia { void set_current_rooms_page(MatrixRoomsPage *rooms_page); void clear_data(); - void sort_rooms(); MatrixQuickMedia *matrix_delegate = nullptr; - bool filter_on_update = false; private: struct TagData { std::shared_ptr<BodyItem> tag_item; std::vector<std::shared_ptr<BodyItem>> room_body_items; }; - std::recursive_mutex mutex; Body *body; std::map<std::string, TagData> tag_body_items_by_name; - std::map<std::string, std::vector<std::shared_ptr<BodyItem>>> add_room_body_items_by_tags; - std::map<std::string, std::vector<std::shared_ptr<BodyItem>>> remove_room_body_items_by_tags; MatrixRoomsPage *current_rooms_page = nullptr; - bool clear_data_on_update = false; - SearchBar *search_bar = nullptr; }; class MatrixInvitesPage : public Page { public: - MatrixInvitesPage(Program *program, Matrix *matrix, Body *body, SearchBar *search_bar); + MatrixInvitesPage(Program *program, Matrix *matrix, Body *body); const char* get_title() const override { return title.c_str(); } PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override; + bool submit_is_async() override { return false; } bool clear_search_after_submit() override { return true; } - void update() override; void add_body_item(std::shared_ptr<BodyItem> body_item); void remove_body_item_by_room_id(const std::string &room_id); void clear_data(); - bool filter_on_update = false; private: Matrix *matrix; - std::mutex mutex; - std::vector<std::shared_ptr<BodyItem>> body_items; - std::vector<std::string> pending_remove_body_items; Body *body; std::string title = "Invites (0)"; size_t prev_invite_count = 0; - bool clear_data_on_update = false; - SearchBar *search_bar = nullptr; }; class MatrixInviteDetailsPage : public Page { @@ -465,14 +421,7 @@ namespace QuickMedia { ~MatrixChatPage(); const char* get_title() const override { return ""; } - PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override { - (void)title; - (void)url; - (void)result_tabs; - return PluginResult::ERR; - } PageTypez get_type() const override { return PageTypez::CHAT; } - void update() override; const std::string room_id; MatrixRoomsPage *rooms_page = nullptr; @@ -510,6 +459,17 @@ namespace QuickMedia { int current_page; }; + class MatrixNotificationsPage : public Page { + public: + MatrixNotificationsPage(Program *program, Body *notifications_body) : Page(program), notifications_body(notifications_body) {} + const char* get_title() const override { return "Notifications (0)"; } + PluginResult submit(const std::string&, const std::string&, std::vector<Tab>&) override { + return PluginResult::OK; + } + private: + Body *notifications_body; + }; + class Matrix { public: // TODO: Make this return the Matrix object instead, to force users to call start_sync @@ -578,6 +538,10 @@ namespace QuickMedia { // Also clears the event queue void disable_event_queue(); + // Calls the |MatrixDelegate| pending events. + // Should be called from the main (ui) thread + void update(); + // Has to be called in the main thread. // Returns nullptr if there are no new events. std::unique_ptr<MatrixEvent> pop_event(); @@ -622,6 +586,7 @@ namespace QuickMedia { std::shared_ptr<UserInfo> get_user_by_id(RoomData *room, const std::string &user_id, bool *is_new_user = nullptr, bool create_if_not_found = true); std::string get_filter_cached(); private: + MessageQueue<std::function<void()>> ui_thread_tasks; std::deque<std::unique_ptr<MatrixEvent>> event_queue; std::mutex event_queue_mutex; RoomData *current_event_queue_room = nullptr; diff --git a/plugins/Page.hpp b/plugins/Page.hpp index 78eb3c4..be6eb76 100644 --- a/plugins/Page.hpp +++ b/plugins/Page.hpp @@ -35,6 +35,8 @@ namespace QuickMedia { (void)result_tabs; return PluginResult::ERR; } + // Override and return false to make submit run in the main (ui) thread + virtual bool submit_is_async() { return true; } virtual bool clear_search_after_submit() { return false; } // Note: If pagination is done by fetching the next page until we get to |page|, then the "current page" should be reset everytime |search| is called. // Note: the first page is 0 @@ -53,9 +55,6 @@ namespace QuickMedia { // This is called both when first navigating to page and when going back to page virtual void on_navigate_to_page(Body *body) { (void)body; } - // Called periodically (every frame right now) if this page is the currently active one - virtual void update() {} - std::unique_ptr<Body> create_body(); std::unique_ptr<SearchBar> create_search_bar(const std::string &placeholder_text, int search_delay); diff --git a/src/Body.cpp b/src/Body.cpp index 4f2e816..d51ea29 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -314,10 +314,12 @@ namespace QuickMedia { void Body::prepend_items(BodyItems new_items) { items.insert(items.begin(), std::make_move_iterator(new_items.begin()), std::make_move_iterator(new_items.end())); + items_set_dirty(); } void Body::append_items(BodyItems new_items) { items.insert(items.end(), std::make_move_iterator(new_items.begin()), std::make_move_iterator(new_items.end())); + items_set_dirty(); } // TODO: Binary search and use hint to start search from start or end (for example when adding "previous" items or "next" items) @@ -336,6 +338,7 @@ namespace QuickMedia { for(auto &new_item : new_items) { insert_item_by_timestamp(new_item); } + items_set_dirty(); } void Body::clear_cache() { @@ -486,7 +489,8 @@ namespace QuickMedia { void Body::draw(sf::RenderWindow &window, sf::Vector2f pos, sf::Vector2f size, const Json::Value &content_progress) { if(items_dirty) { items_dirty = false; - filter_search_fuzzy(current_filter); + if(using_filter) + filter_search_fuzzy(current_filter, false); } sf::Vector2f scissor_pos = pos; @@ -1490,7 +1494,7 @@ namespace QuickMedia { return full_match; } - void Body::filter_search_fuzzy(const std::string &text) { + void Body::filter_search_fuzzy(const std::string &text, bool select_first_if_empty) { current_filter = text; if(text.empty()) { @@ -1498,7 +1502,8 @@ namespace QuickMedia { item->visible = true; } - select_first_item(); + if(select_first_if_empty) + select_first_item(); return; } @@ -1507,6 +1512,7 @@ namespace QuickMedia { } select_first_item(); + using_filter = true; } void Body::filter_search_fuzzy_item(const std::string &text, BodyItem *body_item) { diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 297a780..5bbcfd7 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -1057,6 +1057,9 @@ namespace QuickMedia { auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), HistoryType::MANGA); tabs.push_back(Tab{create_body(), std::move(history_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } else if(strcmp(plugin_name, "manga") == 0) { + auto mangadex = std::make_unique<MangadexSearchPage>(this); + upgrade_legacy_mangadex_ids(this, mangadex.get()); + auto manganelo = std::make_unique<ManganeloSearchPage>(this); auto manganelos = std::make_unique<MangaGenericSearchPage>(this, "manganelos", "http://manganelos.com/"); add_manganelos_handlers(manganelos.get()); @@ -1067,13 +1070,14 @@ namespace QuickMedia { auto readm = std::make_unique<MangaGenericSearchPage>(this, "readm", "https://readm.org/"); add_readm_handlers(readm.get()); + // TODO: Use async task pool std::vector<MangaPlugin> pages; pages.push_back({std::move(manganelo), "Manganelo", "manganelo", resources_root + "images/" + get_plugin_logo_name("manganelo")}); pages.push_back({std::move(manganelos), "Manganelos", "manganelos", resources_root + "images/" + get_plugin_logo_name("manganelos")}); pages.push_back({std::move(mangatown), "Mangatown", "mangatown", resources_root + "images/" + get_plugin_logo_name("mangatown")}); pages.push_back({std::move(mangakatana), "Mangakatana", "mangakatana", resources_root + "images/" + get_plugin_logo_name("mangakatana")}); pages.push_back({std::move(readm), "Readm", "readm", resources_root + "images/" + get_plugin_logo_name("readm")}); - // TODO: Add mangadex + pages.push_back({std::move(mangadex), "Mangadex", "mangadex", resources_root + "images/" + get_plugin_logo_name("mangadex")}); tabs.push_back(Tab{create_body(), std::make_unique<MangaCombinedSearchPage>(this, std::move(pages)), create_search_bar("Search...", 400)}); } else if(strcmp(plugin_name, "nyaa.si") == 0) { @@ -1478,6 +1482,9 @@ namespace QuickMedia { window.draw(tab_associated_data.search_result_text); } + if(matrix) + matrix->update(); + if(matrix && !matrix->is_initial_sync_finished()) { // if(is_login_sync) { load_sprite.setPosition(body_pos.x + body_size.x * 0.5f, body_pos.y + body_size.y * 0.5f); @@ -1558,10 +1565,18 @@ namespace QuickMedia { std::vector<Tab> new_tabs; auto prev_selected_item = tabs[selected_tab].page->submit_body_item; tabs[selected_tab].page->submit_body_item = selected_item; - TaskResult submit_result = run_task_with_loading_screen([&tabs, selected_tab, &selected_item, &search_text, &new_tabs](){ - PluginResult submit_result = tabs[selected_tab].page->submit(selected_item ? selected_item->get_title() : search_text, selected_item ? selected_item->url : "", new_tabs); - return submit_result == PluginResult::OK; - }); + + auto plugin_submit_handler = [&tabs, selected_tab, &selected_item, &search_text, &new_tabs]() { + PluginResult plugin_result = tabs[selected_tab].page->submit(selected_item ? selected_item->get_title() : search_text, selected_item ? selected_item->url : "", new_tabs); + return plugin_result == PluginResult::OK; + }; + + TaskResult submit_result; + if(tabs[selected_tab].page->submit_is_async()) { + submit_result = run_task_with_loading_screen(std::move(plugin_submit_handler)); + } else { + submit_result = plugin_submit_handler() ? TaskResult::TRUE : TaskResult::FALSE; + } if(submit_result == TaskResult::CANCEL) { return; @@ -1883,8 +1898,6 @@ namespace QuickMedia { for(size_t i = 0; i < tabs.size(); ++i) { TabAssociatedData &associated_data = tab_associated_data[i]; - tabs[i].page->update(); - if(associated_data.fetching_next_page_running && associated_data.next_page_future.ready()) { BodyItems new_body_items = associated_data.next_page_future.get(); fprintf(stderr, "Finished fetching page %d, num new items: %zu\n", associated_data.fetched_page + 1, new_body_items.size()); @@ -4612,7 +4625,7 @@ namespace QuickMedia { // Fetch replied to message if(event_data->status == FetchStatus::FINISHED_LOADING && event_data->message) { - if(event_data->message->related_event_id.empty() || (body_item->embedded_item_status != FetchStatus::NONE && body_item->embedded_item_status != FetchStatus::QUEUED_LOADING)) + if(event_data->message->related_event_id.empty() || event_data->message->related_event_type != RelatedEventType::REPLY || (body_item->embedded_item_status != FetchStatus::NONE && body_item->embedded_item_status != FetchStatus::QUEUED_LOADING)) return; if(load_cached_related_embedded_item(body_item, event_data->message, me, current_room, tabs[MESSAGES_TAB_INDEX].body->items)) @@ -4670,7 +4683,7 @@ namespace QuickMedia { if(message_is_timeline(message) && (!last_visible_timeline_message || message->timestamp > last_visible_timeline_message->timestamp)) last_visible_timeline_message = message; - if(message->related_event_id.empty() || (body_item->embedded_item_status != FetchStatus::NONE && body_item->embedded_item_status != FetchStatus::QUEUED_LOADING)) + if(message->related_event_id.empty() || message->related_event_type != RelatedEventType::REPLY || (body_item->embedded_item_status != FetchStatus::NONE && body_item->embedded_item_status != FetchStatus::QUEUED_LOADING)) return; if(fetch_message_future.valid()) { @@ -5375,37 +5388,66 @@ namespace QuickMedia { update_idle_state(); handle_window_close(); + matrix->update(); mention.update(); - matrix_chat_page->update(); + std::unique_ptr<MatrixEvent> matrix_event; + while((matrix_event = matrix->pop_event()) != nullptr) { + if(matrix_event) { + switch(matrix_event->type) { + case MatrixEvent::Type::ADD_USER: + on_add_user_event(static_cast<MatrixAddUserEvent*>(matrix_event.get())); + break; + case MatrixEvent::Type::REMOVE_USER: + on_remove_user_event(static_cast<MatrixRemoveUserEvent*>(matrix_event.get())); + break; + case MatrixEvent::Type::USER_INFO: + on_user_info_event(static_cast<MatrixUserInfoEvent*>(matrix_event.get())); + break; + } + } + } + + while((provisional_message = provisional_message_queue.pop_if_available()) != std::nullopt) { + if(!provisional_message->body_item || !provisional_message->message) + continue; + + if(!provisional_message->event_id.empty()) { + provisional_message->message->event_id = std::move(provisional_message->event_id); + provisional_message->body_item->set_description_color(sf::Color::White); + sent_messages[provisional_message->message->event_id] = std::move(provisional_message.value()); + } else if(provisional_message->body_item) { + provisional_message->body_item->set_description("Failed to send: " + provisional_message->body_item->get_description()); + provisional_message->body_item->set_description_color(sf::Color::Red); + provisional_message->body_item->userdata = nullptr; + } + } switch(new_page) { case PageType::FILE_MANAGER: { new_page = PageType::CHAT; - if(current_room) { - for(ChatTab &tab : tabs) { - tab.body->clear_cache(); - } + for(ChatTab &tab : tabs) { + tab.body->clear_cache(); + } - auto file_manager_page = std::make_unique<FileManagerPage>(this); - file_manager_page->set_current_directory(get_home_dir().data); - auto file_manager_body = create_body(); - file_manager_page->get_files_in_directory(file_manager_body->items); - std::vector<Tab> file_manager_tabs; - file_manager_tabs.push_back(Tab{std::move(file_manager_body), std::move(file_manager_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + auto file_manager_page = std::make_unique<FileManagerPage>(this); + file_manager_page->set_current_directory(get_home_dir().data); + auto file_manager_body = create_body(); + file_manager_page->get_files_in_directory(file_manager_body->items); + std::vector<Tab> file_manager_tabs; + file_manager_tabs.push_back(Tab{std::move(file_manager_body), std::move(file_manager_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); - selected_files.clear(); - page_loop(file_manager_tabs); + selected_files.clear(); + page_loop(file_manager_tabs); - if(selected_files.empty()) { - fprintf(stderr, "No files selected!\n"); - } else { - // TODO: Upload multiple files. - upload_file(selected_files[0]); - } - redraw = true; + if(selected_files.empty()) { + fprintf(stderr, "No files selected!\n"); + } else { + // TODO: Upload multiple files. + upload_file(selected_files[0]); } + redraw = true; break; } case PageType::CHAT_LOGIN: { @@ -5519,38 +5561,6 @@ namespace QuickMedia { logo_sprite.setPosition(body_pos.x - body_padding_horizontal + logo_padding_x, std::floor(window_size.y - chat_input_height_full * 0.5f - logo_size.y * 0.5f)); } - while((provisional_message = provisional_message_queue.pop_if_available()) != std::nullopt) { - if(!provisional_message->body_item || !provisional_message->message) - continue; - - if(!provisional_message->event_id.empty()) { - provisional_message->message->event_id = std::move(provisional_message->event_id); - provisional_message->body_item->set_description_color(sf::Color::White); - sent_messages[provisional_message->message->event_id] = std::move(provisional_message.value()); - } else if(provisional_message->body_item) { - provisional_message->body_item->set_description("Failed to send: " + provisional_message->body_item->get_description()); - provisional_message->body_item->set_description_color(sf::Color::Red); - provisional_message->body_item->userdata = nullptr; - } - } - - std::unique_ptr<MatrixEvent> matrix_event; - while((matrix_event = matrix->pop_event()) != nullptr) { - if(matrix_event) { - switch(matrix_event->type) { - case MatrixEvent::Type::ADD_USER: - on_add_user_event(static_cast<MatrixAddUserEvent*>(matrix_event.get())); - break; - case MatrixEvent::Type::REMOVE_USER: - on_remove_user_event(static_cast<MatrixRemoveUserEvent*>(matrix_event.get())); - break; - case MatrixEvent::Type::USER_INFO: - on_user_info_event(static_cast<MatrixUserInfoEvent*>(matrix_event.get())); - break; - } - } - } - sync_data.messages.clear(); sync_data.pinned_events = std::nullopt; matrix->get_room_sync_data(current_room, sync_data); @@ -5908,17 +5918,17 @@ namespace QuickMedia { if(!window.isOpen()) exit(exit_code); - auto rooms_body = create_body(); - auto matrix_rooms_page_search_bar = create_search_bar("Search...", SEARCH_DELAY_FILTER); - auto matrix_rooms_page = std::make_unique<MatrixRoomsPage>(this, rooms_body.get(), "All rooms", nullptr, matrix_rooms_page_search_bar.get()); + auto notifications_body = create_body(); + auto matrix_notifications_page = std::make_unique<MatrixNotificationsPage>(this, notifications_body.get()); auto rooms_tags_body = create_body(); - auto matrix_rooms_tage_page_search_bar = create_search_bar("Search...", SEARCH_DELAY_FILTER); - auto matrix_rooms_tag_page = std::make_unique<MatrixRoomTagsPage>(this, rooms_tags_body.get(), matrix_rooms_tage_page_search_bar.get()); + auto matrix_rooms_tag_page = std::make_unique<MatrixRoomTagsPage>(this, rooms_tags_body.get()); + + auto rooms_body = create_body(); + auto matrix_rooms_page = std::make_unique<MatrixRoomsPage>(this, rooms_body.get(), "All rooms", nullptr); auto invites_body = create_body(); - auto matrix_invites_page_search_bar = create_search_bar("Search...", SEARCH_DELAY_FILTER); - auto matrix_invites_page = std::make_unique<MatrixInvitesPage>(this, matrix, invites_body.get(), matrix_invites_page_search_bar.get()); + auto matrix_invites_page = std::make_unique<MatrixInvitesPage>(this, matrix, invites_body.get()); auto room_directory_body = create_body(); add_body_item_unique_title(room_directory_body->items, matrix->get_homeserver_domain()); @@ -5948,13 +5958,14 @@ namespace QuickMedia { is_login_sync = !sync_cached; std::vector<Tab> tabs; - tabs.push_back(Tab{std::move(rooms_body), std::move(matrix_rooms_page), std::move(matrix_rooms_page_search_bar)}); - tabs.push_back(Tab{std::move(rooms_tags_body), std::move(matrix_rooms_tag_page), std::move(matrix_rooms_tage_page_search_bar)}); - tabs.push_back(Tab{std::move(invites_body), std::move(matrix_invites_page), std::move(matrix_invites_page_search_bar)}); + tabs.push_back(Tab{std::move(notifications_body), std::move(matrix_notifications_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + tabs.push_back(Tab{std::move(rooms_tags_body), std::move(matrix_rooms_tag_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + tabs.push_back(Tab{std::move(rooms_body), std::move(matrix_rooms_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + tabs.push_back(Tab{std::move(invites_body), std::move(matrix_invites_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); tabs.push_back(Tab{std::move(room_directory_body), std::move(matrix_room_directory_page), create_search_bar("Server to search on...", SEARCH_DELAY_FILTER)}); while(window.isOpen()) { - page_loop(tabs); + page_loop(tabs, 2); } matrix->stop_sync(); diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index 4328888..8a0f486 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -349,14 +349,16 @@ namespace QuickMedia { } MatrixQuickMedia::MatrixQuickMedia(Program *program, Matrix *matrix, MatrixRoomsPage *rooms_page, MatrixRoomTagsPage *room_tags_page, MatrixInvitesPage *invites_page) : - program(program), matrix(matrix), rooms_page(rooms_page), room_tags_page(room_tags_page), invites_page(invites_page) + program(program), matrix(matrix), chat_page(nullptr), rooms_page(rooms_page), room_tags_page(room_tags_page), invites_page(invites_page) { rooms_page->matrix_delegate = this; room_tags_page->matrix_delegate = this; } void MatrixQuickMedia::join_room(RoomData *room) { - std::lock_guard<std::mutex> lock(room_body_items_mutex); + if(room_body_item_by_room.find(room) != room_body_item_by_room.end()) + return; + std::string room_name = room->get_name(); if(room_name.empty()) room_name = room->id; @@ -368,12 +370,11 @@ namespace QuickMedia { body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE; body_item->thumbnail_size = sf::Vector2i(32, 32); room->body_item = body_item; - rooms_page->add_body_item(body_item); room_body_item_by_room[room] = body_item; + rooms_page->add_body_item(body_item); } void MatrixQuickMedia::leave_room(RoomData *room, LeaveType leave_type, const std::string &reason) { - std::lock_guard<std::mutex> lock(room_body_items_mutex); room_body_item_by_room.erase(room); rooms_page->remove_body_item_by_room_id(room->id); room_tags_page->remove_body_item_by_room_id(room->id); @@ -382,22 +383,35 @@ namespace QuickMedia { } void MatrixQuickMedia::room_add_tag(RoomData *room, const std::string &tag) { - std::lock_guard<std::mutex> lock(room_body_items_mutex); - room_tags_page->add_room_body_item_to_tag(room_body_item_by_room[room], tag); + auto it = room_body_item_by_room.find(room); + if(it == room_body_item_by_room.end()) + return; + room_tags_page->add_room_body_item_to_tag(it->second, tag); } void MatrixQuickMedia::room_remove_tag(RoomData *room, const std::string &tag) { - std::lock_guard<std::mutex> lock(room_body_items_mutex); - room_tags_page->remove_room_body_item_from_tag(room_body_item_by_room[room], tag); + auto it = room_body_item_by_room.find(room); + if(it == room_body_item_by_room.end()) + return; + room_tags_page->remove_room_body_item_from_tag(it->second, tag); } void MatrixQuickMedia::room_add_new_messages(RoomData *room, const Messages &messages, bool is_initial_sync, bool sync_is_cache, MessageDirection message_dir) { - std::lock_guard<std::mutex> lock(pending_room_messages_mutex); - auto &room_messages_data = pending_room_messages[room]; - room_messages_data.messages.insert(room_messages_data.messages.end(), messages.begin(), messages.end()); - room_messages_data.is_initial_sync = is_initial_sync; - room_messages_data.sync_is_cache = sync_is_cache; - room_messages_data.message_dir = message_dir; + bool is_window_focused = program->is_window_focused(); + RoomData *current_room = program->get_current_chat_room(); + + if(!sync_is_cache && message_dir == MessageDirection::AFTER && !is_initial_sync) { + for(auto &message : messages) { + 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); + } + } + } + } + + update_room_description(room, messages, is_initial_sync, sync_is_cache); } void MatrixQuickMedia::add_invite(const std::string &room_id, const Invite &invite) { @@ -419,34 +433,11 @@ namespace QuickMedia { invites_page->remove_body_item_by_room_id(room_id); } - void MatrixQuickMedia::add_unread_notification(RoomData *room, std::string event_id, std::string sender, std::string body) { - std::lock_guard<std::mutex> lock(room_body_items_mutex); - Notification notification; - notification.event_id = std::move(event_id); - notification.sender = std::move(sender); - notification.body = std::move(body); - unread_notifications[room].push_back(std::move(notification)); + void MatrixQuickMedia::add_unread_notification(RoomData *room, std::string, std::string sender, std::string body) { + show_notification("QuickMedia matrix - " + sender + " (" + room->get_name() + ")", body); } static void sort_room_body_items(std::vector<std::shared_ptr<BodyItem>> &room_body_items) { - #if 0 - std::sort(room_body_items.begin(), room_body_items.end(), [](const std::shared_ptr<BodyItem> &body_item1, const std::shared_ptr<BodyItem> &body_item2) { - return strcasecmp(body_item1->get_title().c_str(), body_item2->get_title().c_str()) < 0; - }); - #if 1 - std::sort(room_body_items.begin(), room_body_items.end(), [](const std::shared_ptr<BodyItem> &body_item1, const std::shared_ptr<BodyItem> &body_item2) { - RoomData *room1 = static_cast<RoomData*>(body_item1->userdata); - RoomData *room2 = static_cast<RoomData*>(body_item2->userdata); - int room1_focus_sum = (int)(room1->unread_notification_count > 0) + (int)!room1->last_message_read; - int room2_focus_sum = (int)(room2->unread_notification_count > 0) + (int)!room2->last_message_read; - return room1_focus_sum > room2_focus_sum; - }); - #else - std::sort(room_body_items.begin(), room_body_items.end(), [](const std::shared_ptr<BodyItem> &body_item1, const std::shared_ptr<BodyItem> &body_item2) { - return strcasecmp(body_item1->get_title().c_str(), body_item2->get_title().c_str()) < 0; - }); - #endif - #endif std::sort(room_body_items.begin(), room_body_items.end(), [](const std::shared_ptr<BodyItem> &body_item1, const std::shared_ptr<BodyItem> &body_item2) { RoomData *room1 = static_cast<RoomData*>(body_item1->userdata); RoomData *room2 = static_cast<RoomData*>(body_item2->userdata); @@ -456,6 +447,18 @@ namespace QuickMedia { }); } + static void insert_room_body_item_by_timestamp(BodyItems &body_items, std::shared_ptr<BodyItem> new_body_item) { + RoomData *new_room = static_cast<RoomData*>(new_body_item->userdata); + for(auto it = body_items.begin(), end = body_items.end(); it != end; ++it) { + RoomData *room = static_cast<RoomData*>((*it)->userdata); + if(new_room->last_message_timestamp >= room->last_message_timestamp) { + body_items.insert(it, std::move(new_body_item)); + return; + } + } + body_items.push_back(std::move(new_body_item)); + } + void body_set_selected_item(Body *body, BodyItem *selected_item) { for(size_t i = 0; i < body->items.size(); ++i) { if(body->items[i]->url == selected_item->url) { @@ -466,24 +469,7 @@ namespace QuickMedia { } } - void MatrixQuickMedia::update(MatrixPageType page_type, Body *chat_body, bool messages_tab_visible) { - update_pending_room_messages(page_type, chat_body, messages_tab_visible); - std::lock_guard<std::mutex> room_body_lock(room_body_items_mutex); - for(auto &it : unread_notifications) { - for(auto &unread_notification : it.second) { - show_notification("QuickMedia matrix - " + unread_notification.sender + " (" + it.first->get_name() + ")", unread_notification.body); - } - } - //if(!unread_notifications.empty()) { - // rooms_page->sort_rooms(); - // room_tags_page->sort_rooms(); - //} - unread_notifications.clear(); - } - void MatrixQuickMedia::clear_data() { - std::lock_guard<std::mutex> lock(pending_room_messages_mutex); - std::lock_guard<std::mutex> room_body_lock(room_body_items_mutex); //room_body_item_by_room.clear(); //pending_room_messages.clear(); //rooms_page->clear_data(); @@ -493,22 +479,19 @@ namespace QuickMedia { } static std::shared_ptr<Message> get_last_message_by_timestamp(const Messages &messages) { - #if 0 - return *std::max_element(messages.begin(), messages.end(), [](const std::shared_ptr<Message> &message1, const std::shared_ptr<Message> &message2) { - return message1->timestamp < message2->timestamp; - }); - #else if(messages.empty()) return nullptr; + size_t last_message_index = 0; for(size_t i = 1; i < messages.size(); ++i) { if(message_is_timeline(messages[i].get()) && messages[i]->timestamp >= messages[last_message_index]->timestamp) last_message_index = i; } + if(message_is_timeline(messages[last_message_index].get())) return messages[last_message_index]; + return nullptr; - #endif } static std::string message_to_room_description_text(Message *message) { @@ -523,7 +506,7 @@ namespace QuickMedia { return extract_first_line_remove_newline_elipses(body, 150); } - void MatrixQuickMedia::update_room_description(RoomData *room, Messages &new_messages, bool is_initial_sync, bool sync_is_cache, Body *chat_body, bool messages_tab_visible) { + void MatrixQuickMedia::update_room_description(RoomData *room, const Messages &new_messages, bool is_initial_sync, bool sync_is_cache) { time_t read_marker_message_timestamp = 0; std::shared_ptr<UserInfo> me = matrix->get_me(room); std::string my_user_read_marker; @@ -570,7 +553,8 @@ namespace QuickMedia { if(last_unread_message && !sync_is_cache) { bool is_window_focused = program->is_window_focused(); RoomData *current_room = program->get_current_chat_room(); - bool set_room_as_unread = !is_window_focused || room != current_room || (!chat_body || !chat_body->is_last_item_fully_visible()) || !messages_tab_visible; + Body *chat_body = chat_page ? chat_page->chat_body : nullptr; + bool set_room_as_unread = !is_window_focused || room != current_room || (!chat_body || !chat_body->is_last_item_fully_visible()) || (chat_page && !chat_page->messages_tab_visible); std::string room_desc; if(set_room_as_unread) @@ -597,35 +581,7 @@ namespace QuickMedia { } } - void MatrixQuickMedia::update_pending_room_messages(MatrixPageType page_type, Body *chat_body, bool messages_tab_visible) { - std::lock_guard<std::mutex> lock(pending_room_messages_mutex); - bool is_window_focused = program->is_window_focused(); - RoomData *current_room = program->get_current_chat_room(); - for(auto &it : pending_room_messages) { - RoomData *room = it.first; - auto &messages = it.second.messages; - bool is_initial_sync = it.second.is_initial_sync; - //auto &room_body_item = room_body_item_by_room[room]; - //std::string room_desc = extract_first_line_remove_newline_elipses(matrix->message_get_author_displayname(it.second.back().get()), AUTHOR_MAX_LENGTH) + ": " + extract_first_line_remove_newline_elipses(it.second.back()->body, 150); - //room_body_item->set_description(std::move(room_desc)); - - if(!it.second.sync_is_cache && it.second.message_dir == MessageDirection::AFTER && !is_initial_sync) { - for(auto &message : messages) { - 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); - } - } - } - } - - update_room_description(room, messages, is_initial_sync, it.second.sync_is_cache, chat_body, messages_tab_visible); - } - pending_room_messages.clear(); - } - - MatrixRoomsPage::MatrixRoomsPage(Program *program, Body *body, std::string title, MatrixRoomTagsPage *room_tags_page, SearchBar *search_bar) : Page(program), body(body), title(std::move(title)), room_tags_page(room_tags_page), search_bar(search_bar) { + MatrixRoomsPage::MatrixRoomsPage(Program *program, Body *body, std::string title, MatrixRoomTagsPage *room_tags_page) : Page(program), body(body), title(std::move(title)), room_tags_page(room_tags_page) { if(room_tags_page) room_tags_page->set_current_rooms_page(this); } @@ -635,90 +591,19 @@ namespace QuickMedia { room_tags_page->set_current_rooms_page(nullptr); } - PluginResult MatrixRoomsPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) { - (void)title; + PluginResult MatrixRoomsPage::submit(const std::string&, const std::string &url, std::vector<Tab> &result_tabs) { auto chat_page = std::make_unique<MatrixChatPage>(program, url, this); result_tabs.push_back(Tab{nullptr, std::move(chat_page), nullptr}); return PluginResult::OK; } - void MatrixRoomsPage::on_navigate_to_page(Body *body) { - if(search_bar) - body->filter_search_fuzzy(search_bar->get_text()); - //sort_room_body_items(body->items); - } - - void MatrixRoomsPage::update() { - { - std::lock_guard<std::mutex> lock(mutex); - int prev_selected_item = body->get_selected_item(); - if(clear_data_on_update) { - clear_data_on_update = false; - body->clear_items(); - } - - if(!pending_remove_body_items.empty() || !room_body_items.empty()) { - sort_on_update = true; - filter_on_update = true; - } - - for(const std::string &room_id : pending_remove_body_items) { - remove_body_item_by_url(body->items, room_id); - // TODO: There can be a race condition where current_chat_page is set after entering a room and then we will enter a room we left - if(current_chat_page && current_chat_page->room_id == room_id) { - program->set_go_to_previous_page(); - body->select_first_item(); - current_chat_page = nullptr; - } - } - - pending_remove_body_items.clear(); - body->set_selected_item(prev_selected_item, false); - body->clamp_selection(); - body->append_items(std::move(room_body_items)); - } - if(sort_on_update) { - sort_on_update = false; - //BodyItem *selected_item = body->get_selected(); - sort_room_body_items(body->items); - //body_set_selected_item(body, selected_item); - } - matrix_delegate->update(MatrixPageType::ROOM_LIST, nullptr, false); - if(filter_on_update) { - filter_on_update = false; - if(search_bar) - body->filter_search_fuzzy(search_bar->get_text()); - //sort_room_body_items(body->items); - } - } - void MatrixRoomsPage::add_body_item(std::shared_ptr<BodyItem> body_item) { - std::lock_guard<std::mutex> lock(mutex); - room_body_items.push_back(body_item); + insert_room_body_item_by_timestamp(body->items, body_item); } void MatrixRoomsPage::move_room_to_top(RoomData *room) { // Swap order of rooms in body list to put rooms with mentions at the top and then unread messages and then all the other rooms - // TODO: Optimize with hash map instead of linear search? or cache the index - std::lock_guard<std::mutex> lock(mutex); -#if 0 - int room_body_index = body->get_index_by_body_item(room->body_item.get()); - if(room_body_index != -1) { - std::shared_ptr<BodyItem> body_item = body->items[room_body_index]; - int body_swap_index = -1; - if(room->unread_notification_count > 0) - body_swap_index = find_top_body_position_for_mentioned_room(body->items, body_item.get()); - else if(!room->last_message_read) - body_swap_index = find_top_body_position_for_unread_room(body->items, body_item.get()); - if(body_swap_index != -1 && body_swap_index != room_body_index) { - body->items.erase(body->items.begin() + room_body_index); - if(body_swap_index <= room_body_index) - body->items.insert(body->items.begin() + body_swap_index, std::move(body_item)); - else - body->items.insert(body->items.begin() + (body_swap_index - 1), std::move(body_item)); - } - } -#else + // TODO: Optimize with binary search of linear search? or cache the index int room_body_index = body->get_index_by_body_item(room->body_item.get()); if(room_body_index == -1) return; @@ -732,6 +617,7 @@ namespace QuickMedia { RoomData *room_i = static_cast<RoomData*>(body->items[i]->userdata); if((int)i == room_body_index) return; + if((int)i != selected_item && room_i && room->last_message_timestamp >= room_i->last_message_timestamp) { auto body_item_to_insert = body->items[room_body_index]; body->items.erase(body->items.begin() + room_body_index); @@ -746,35 +632,28 @@ namespace QuickMedia { return; } } -#endif } void MatrixRoomsPage::remove_body_item_by_room_id(const std::string &room_id) { - std::lock_guard<std::mutex> lock(mutex); - pending_remove_body_items.push_back(room_id); + remove_body_item_by_url(body->items, room_id); + if(current_chat_page && current_chat_page->room_id == room_id) { + program->set_go_to_previous_page(); + body->select_first_item(); + current_chat_page = nullptr; + } } void MatrixRoomsPage::set_current_chat_page(MatrixChatPage *chat_page) { - std::lock_guard<std::mutex> lock(mutex); current_chat_page = chat_page; } void MatrixRoomsPage::clear_data() { - std::lock_guard<std::mutex> lock(mutex); - room_body_items.clear(); - pending_remove_body_items.clear(); - clear_data_on_update = true; + body->clear_items(); if(current_chat_page) current_chat_page->should_clear_data = true; } - void MatrixRoomsPage::sort_rooms() { - sort_on_update = true; - } - - PluginResult MatrixRoomTagsPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) { - (void)title; - std::lock_guard<std::recursive_mutex> lock(mutex); + PluginResult MatrixRoomTagsPage::submit(const std::string&, const std::string &url, std::vector<Tab> &result_tabs) { auto body = create_body(); Body *body_ptr = body.get(); TagData &tag_data = tag_body_items_by_name[url]; @@ -782,105 +661,68 @@ namespace QuickMedia { //BodyItem *selected_item = body->get_selected(); sort_room_body_items(body->items); //body_set_selected_item(body.get(), selected_item); - auto search_bar = create_search_bar("Search...", SEARCH_DELAY_FILTER); - auto rooms_page = std::make_unique<MatrixRoomsPage>(program, body_ptr, tag_data.tag_item->get_title(), this, search_bar.get()); + auto rooms_page = std::make_unique<MatrixRoomsPage>(program, body_ptr, tag_data.tag_item->get_title(), this); rooms_page->matrix_delegate = matrix_delegate; - result_tabs.push_back(Tab{std::move(body), std::move(rooms_page), std::move(search_bar)}); + result_tabs.push_back(Tab{std::move(body), std::move(rooms_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); return PluginResult::OK; } - // TODO: Also add/remove body items to above body (in submit) - void MatrixRoomTagsPage::update() { - { - std::lock_guard<std::recursive_mutex> lock(mutex); - int prev_selected_item = body->get_selected_item(); - if(clear_data_on_update) { - clear_data_on_update = false; - body->clear_items(); - } - - for(auto &it : remove_room_body_items_by_tags) { - auto tag_body_it = tag_body_items_by_name.find(it.first); - if(tag_body_it == tag_body_items_by_name.end()) - continue; - - for(auto &room_to_remove : it.second) { - auto room_body_item_it = std::find(tag_body_it->second.room_body_items.begin(), tag_body_it->second.room_body_items.end(), room_to_remove); - if(room_body_item_it != tag_body_it->second.room_body_items.end()) - tag_body_it->second.room_body_items.erase(room_body_item_it); - } - - if(tag_body_it->second.room_body_items.empty()) { - auto room_body_item_it = std::find(body->items.begin(), body->items.end(), tag_body_it->second.tag_item); - if(room_body_item_it != body->items.end()) { - body->items.erase(room_body_item_it); - filter_on_update = true; - } - tag_body_items_by_name.erase(tag_body_it); - } + void MatrixRoomTagsPage::add_room_body_item_to_tag(std::shared_ptr<BodyItem> body_item, const std::string &tag) { + TagData *tag_data; + auto tag_body_it = tag_body_items_by_name.find(tag); + if(tag_body_it == tag_body_items_by_name.end()) { + std::string tag_name = tag_get_name(tag); + if(tag_name.empty()) { + return; + } else { + auto tag_body_item = BodyItem::create(std::move(tag_name)); + tag_body_item->url = tag; + tag_body_items_by_name.insert(std::make_pair(tag, TagData{tag_body_item, {}})); + // TODO: Sort by tag priority + body->items.push_back(tag_body_item); + body->items_set_dirty(); + tag_data = &tag_body_items_by_name[tag]; + tag_data->tag_item = tag_body_item; } - remove_room_body_items_by_tags.clear(); - - for(auto &it : add_room_body_items_by_tags) { - TagData *tag_data; - auto tag_body_it = tag_body_items_by_name.find(it.first); - if(tag_body_it == tag_body_items_by_name.end()) { - std::string tag_name = tag_get_name(it.first); - if(!tag_name.empty()) { - auto tag_body_item = BodyItem::create(std::move(tag_name)); - tag_body_item->url = it.first; - tag_body_items_by_name.insert(std::make_pair(it.first, TagData{tag_body_item, {}})); - // TODO: Sort by tag priority - body->items.push_back(tag_body_item); - tag_data = &tag_body_items_by_name[it.first]; - tag_data->tag_item = tag_body_item; - filter_on_update = true; - } - } else { - tag_data = &tag_body_it->second; - } + } else { + tag_data = &tag_body_it->second; + } - for(auto &room_body_item : it.second) { - bool already_exists = false; - for(auto &body_it : tag_data->room_body_items) { - if(body_it->userdata == room_body_item->userdata) { - already_exists = true; - break; - } - } - if(!already_exists) - tag_data->room_body_items.push_back(room_body_item); - } + bool already_exists = false; + for(auto &body_it : tag_data->room_body_items) { + if(body_it->userdata == body_item->userdata) { + already_exists = true; + break; } - add_room_body_items_by_tags.clear(); - body->set_selected_item(prev_selected_item, false); } - matrix_delegate->update(MatrixPageType::ROOM_LIST, nullptr, false); - if(filter_on_update) { - filter_on_update = false; - if(search_bar) - body->filter_search_fuzzy(search_bar->get_text()); - } - } - void MatrixRoomTagsPage::add_room_body_item_to_tag(std::shared_ptr<BodyItem> body_item, const std::string &tag) { - std::lock_guard<std::recursive_mutex> lock(mutex); - add_room_body_items_by_tags[tag].push_back(body_item); + if(!already_exists) + tag_data->room_body_items.push_back(body_item); } void MatrixRoomTagsPage::remove_room_body_item_from_tag(std::shared_ptr<BodyItem> body_item, const std::string &tag) { - std::lock_guard<std::recursive_mutex> lock(mutex); - remove_room_body_items_by_tags[tag].push_back(body_item); + auto tag_body_it = tag_body_items_by_name.find(tag); + if(tag_body_it == tag_body_items_by_name.end()) + return; + + auto room_body_item_it = std::find(tag_body_it->second.room_body_items.begin(), tag_body_it->second.room_body_items.end(), body_item); + if(room_body_item_it != tag_body_it->second.room_body_items.end()) + tag_body_it->second.room_body_items.erase(room_body_item_it); + + if(tag_body_it->second.room_body_items.empty()) { + auto room_body_item_it = std::find(body->items.begin(), body->items.end(), tag_body_it->second.tag_item); + if(room_body_item_it != body->items.end()) + body->items.erase(room_body_item_it); + tag_body_items_by_name.erase(tag_body_it); + } } void MatrixRoomTagsPage::move_room_to_top(RoomData *room) { - std::lock_guard<std::recursive_mutex> lock(mutex); if(current_rooms_page) current_rooms_page->move_room_to_top(room); } void MatrixRoomTagsPage::remove_body_item_by_room_id(const std::string &room_id) { - std::lock_guard<std::recursive_mutex> lock(mutex); for(auto it = tag_body_items_by_name.begin(); it != tag_body_items_by_name.end();) { remove_body_item_by_url(it->second.room_body_items, room_id); if(it->second.room_body_items.empty()) @@ -888,32 +730,23 @@ namespace QuickMedia { else ++it; } + if(current_rooms_page) current_rooms_page->remove_body_item_by_room_id(room_id); } void MatrixRoomTagsPage::set_current_rooms_page(MatrixRoomsPage *rooms_page) { - std::lock_guard<std::recursive_mutex> lock(mutex); current_rooms_page = rooms_page; } void MatrixRoomTagsPage::clear_data() { - std::lock_guard<std::recursive_mutex> lock(mutex); tag_body_items_by_name.clear(); - add_room_body_items_by_tags.clear(); - remove_room_body_items_by_tags.clear(); - clear_data_on_update = true; + body->clear_items(); if(current_rooms_page) current_rooms_page->clear_data(); } - void MatrixRoomTagsPage::sort_rooms() { - std::lock_guard<std::recursive_mutex> lock(mutex); - if(current_rooms_page) - current_rooms_page->sort_rooms(); - } - - MatrixInvitesPage::MatrixInvitesPage(Program *program, Matrix *matrix, Body *body, SearchBar *search_bar) : Page(program), matrix(matrix), body(body), search_bar(search_bar) { + MatrixInvitesPage::MatrixInvitesPage(Program *program, Matrix *matrix, Body *body) : Page(program), matrix(matrix), body(body) { } @@ -948,71 +781,35 @@ namespace QuickMedia { return PluginResult::OK; } - void MatrixInvitesPage::update() { - std::lock_guard<std::mutex> lock(mutex); - - int prev_selected_item = body->get_selected_item(); - if(clear_data_on_update) { - clear_data_on_update = false; - body->clear_items(); - } - - if(!pending_remove_body_items.empty() || !body_items.empty()) - filter_on_update = true; - - for(const std::string &room_id : pending_remove_body_items) { - remove_body_item_by_url(body->items, room_id); - } - pending_remove_body_items.clear(); - body->set_selected_item(prev_selected_item, false); - body->clamp_selection(); - + void MatrixInvitesPage::add_body_item(std::shared_ptr<BodyItem> body_item) { // TODO: Insert in reverse order (to show the latest invite at the top?) - body->insert_items_by_timestamps(std::move(body_items)); + body->insert_item_by_timestamp(std::move(body_item)); + body->items_set_dirty(); if(body->items.size() != prev_invite_count) { prev_invite_count = body->items.size(); title = "Invites (" + std::to_string(body->items.size()) + ")"; } - - if(filter_on_update) { - filter_on_update = false; - if(search_bar) - body->filter_search_fuzzy(search_bar->get_text()); - } - } - - void MatrixInvitesPage::add_body_item(std::shared_ptr<BodyItem> body_item) { - std::lock_guard<std::mutex> lock(mutex); - body_items.push_back(std::move(body_item)); } void MatrixInvitesPage::remove_body_item_by_room_id(const std::string &room_id) { - std::lock_guard<std::mutex> lock(mutex); - pending_remove_body_items.push_back(room_id); + remove_body_item_by_url(body->items, room_id); } void MatrixInvitesPage::clear_data() { - std::lock_guard<std::mutex> lock(mutex); - body_items.clear(); - pending_remove_body_items.clear(); - title = "Invites (0)"; + body->clear_items(); prev_invite_count = 0; - clear_data_on_update = true; + title = "Invites (0)"; } MatrixChatPage::MatrixChatPage(Program *program, std::string room_id, MatrixRoomsPage *rooms_page) : Page(program), room_id(std::move(room_id)), rooms_page(rooms_page) { assert(rooms_page); rooms_page->set_current_chat_page(this); + rooms_page->matrix_delegate->chat_page = this; } MatrixChatPage::~MatrixChatPage() { rooms_page->set_current_chat_page(nullptr); - } - - void MatrixChatPage::update() { - rooms_page->matrix_delegate->update(MatrixPageType::CHAT, chat_body, messages_tab_visible); - if(rooms_page) - rooms_page->update(); + rooms_page->matrix_delegate->chat_page = nullptr; } PluginResult MatrixRoomDirectoryPage::submit(const std::string &title, const std::string&, std::vector<Tab> &result_tabs) { @@ -1466,7 +1263,9 @@ namespace QuickMedia { 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()); - delegate->add_unread_notification(room, std::move(event_id), std::move(sender), std::move(body)); + 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)); + }); } return PluginResult::OK; } @@ -1586,7 +1385,7 @@ namespace QuickMedia { } if(is_new_room) - delegate->join_room(room); + ui_thread_tasks.push([this, room]{ delegate->join_room(room); }); events_add_messages(events_json, room, MessageDirection::AFTER, has_unread_notifications); if(!is_additional_messages_sync) @@ -1600,12 +1399,12 @@ namespace QuickMedia { } if(is_new_room) - delegate->join_room(room); + ui_thread_tasks.push([this, room]{ delegate->join_room(room); }); } if(remove_invite(room_id_str)) { // TODO: Show leave type and reason and who caused the invite to be removed - delegate->remove_invite(room_id_str); + ui_thread_tasks.push([this, room_id_str{std::move(room_id_str)}]{ delegate->remove_invite(room_id_str); }); } if(account_data_json.IsObject()) { @@ -1618,7 +1417,7 @@ namespace QuickMedia { std::set<std::string> &room_tags = room->get_tags_unsafe(); if(room_tags.empty()) { room_tags.insert(OTHERS_ROOM_TAG); - delegate->room_add_tag(room, OTHERS_ROOM_TAG); + ui_thread_tasks.push([this, room]{ delegate->room_add_tag(room, OTHERS_ROOM_TAG); }); } room->release_room_lock(); } @@ -1637,7 +1436,8 @@ namespace QuickMedia { std::lock_guard<std::recursive_mutex> lock(room_data_mutex); for(auto &room : rooms) { if(existing_rooms.find(room.get()) == existing_rooms.end()) { - delegate->leave_room(room.get(), LeaveType::LEAVE, ""); + RoomData *room_p = room.get(); + ui_thread_tasks.push([this, room_p]{ delegate->leave_room(room_p, LeaveType::LEAVE, ""); }); remove_room(room->id); } } @@ -1925,8 +1725,13 @@ namespace QuickMedia { } } - if(delegate) - delegate->room_add_new_messages(room_data, new_messages, next_batch.empty(), sync_is_cache, message_dir); + if(delegate) { + bool cache_sync = sync_is_cache; + bool is_initial_sync = next_batch.empty(); + ui_thread_tasks.push([this, room_data, cache_sync, new_messages{std::move(new_messages)}, is_initial_sync, message_dir]{ + delegate->room_add_new_messages(room_data, new_messages, is_initial_sync, cache_sync, message_dir); + }); + } return num_new_messages; } @@ -2472,18 +2277,18 @@ namespace QuickMedia { for(const std::string &room_tag : room_tags) { auto it = new_tags.find(room_tag); if(it == new_tags.end()) - delegate->room_remove_tag(room_data, room_tag); + ui_thread_tasks.push([this, room_data, room_tag]{ delegate->room_remove_tag(room_data, room_tag); }); } for(const std::string &new_tag : new_tags) { auto it = room_tags.find(new_tag); if(it == room_tags.end()) - delegate->room_add_tag(room_data, new_tag); + ui_thread_tasks.push([this, room_data, new_tag]{ delegate->room_add_tag(room_data, new_tag); }); } if(new_tags.empty()) { new_tags.insert(OTHERS_ROOM_TAG); - delegate->room_add_tag(room_data, OTHERS_ROOM_TAG); + ui_thread_tasks.push([this, room_data]{ delegate->room_add_tag(room_data, OTHERS_ROOM_TAG); }); } room_tags = std::move(new_tags); @@ -2556,7 +2361,7 @@ namespace QuickMedia { std::string room_id_str(room_id.GetString(), room_id.GetStringLength()); if(set_invite(room_id_str, invite)) - delegate->add_invite(room_id_str, std::move(invite)); + ui_thread_tasks.push([this, room_id_str{std::move(room_id_str)}, invite{std::move(invite)}]{ delegate->add_invite(room_id_str, std::move(invite)); }); break; } @@ -2579,7 +2384,7 @@ namespace QuickMedia { std::string room_id_str(room_id.GetString(), room_id.GetStringLength()); if(remove_invite(room_id_str)) { // TODO: Show leave type and reason and who caused the invite to be removed - delegate->remove_invite(room_id_str); + ui_thread_tasks.push([this, room_id_str{std::move(room_id_str)}]{ delegate->remove_invite(room_id_str); }); } const rapidjson::Value &timeline_json = GetMember(it.value, "timeline"); @@ -2638,7 +2443,7 @@ namespace QuickMedia { if(!reason_str.empty()) desc += ", reason: " + reason_str; - delegate->leave_room(room, leave_type, desc); + ui_thread_tasks.push([this, room, leave_type, desc{std::move(desc)}]{ delegate->leave_room(room, leave_type, desc); }); remove_room(room_id_str); break; } @@ -3047,9 +2852,7 @@ namespace QuickMedia { "</mx-reply>" + std::move(formatted_body); } - // TODO: Support greentext PluginResult Matrix::post_reply(RoomData *room, const std::string &body, void *relates_to, std::string &event_id_response, const std::string &custom_transaction_id) { - // TODO: Store shared_ptr<Message> instead of raw pointer... Message *relates_to_message_raw = (Message*)relates_to; std::string transaction_id = custom_transaction_id; @@ -3133,7 +2936,7 @@ namespace QuickMedia { std::string formatted_body_edit_str; rapidjson::Document request_data(rapidjson::kObjectType); - request_data.AddMember("msgtype", "m.text", request_data.GetAllocator()); // TODO: Allow other types of edits + request_data.AddMember("msgtype", "m.text", request_data.GetAllocator()); request_data.AddMember("body", rapidjson::StringRef(body_edit_str.c_str()), request_data.GetAllocator()); if(!formatted_body.empty()) { formatted_body_edit_str = " * " + formatted_body; @@ -3772,12 +3575,12 @@ namespace QuickMedia { room = new_room.get(); add_room(std::move(new_room)); - delegate->join_room(room); + ui_thread_tasks.push([this, room]{ delegate->join_room(room); }); room->acquire_room_lock(); std::set<std::string> &room_tags = room->get_tags_unsafe(); if(room_tags.empty()) { room_tags.insert(OTHERS_ROOM_TAG); - delegate->room_add_tag(room, OTHERS_ROOM_TAG); + ui_thread_tasks.push([this, room]{ delegate->room_add_tag(room, OTHERS_ROOM_TAG); }); } room->release_room_lock(); } @@ -3799,7 +3602,7 @@ namespace QuickMedia { if(download_result == DownloadResult::OK) { RoomData *room = get_room_by_id(room_id); if(room) { - delegate->leave_room(room, LeaveType::LEAVE, ""); + ui_thread_tasks.push([this, room]{ delegate->leave_room(room, LeaveType::LEAVE, ""); }); remove_room(room_id); } } @@ -4019,7 +3822,7 @@ namespace QuickMedia { // We intentionally dont clear |rooms| here because we want the objects inside it to still be valid. TODO: Clear |rooms| here //room_data_by_id.clear(); invites.clear(); - delegate->clear_data(); + ui_thread_tasks.push([this]{ delegate->clear_data(); }); } std::shared_ptr<UserInfo> Matrix::get_user_by_id(RoomData *room, const std::string &user_id, bool *is_new_user, bool create_if_not_found) { @@ -4184,6 +3987,13 @@ namespace QuickMedia { event_queue.clear(); } + void Matrix::update() { + std::optional<std::function<void()>> task; + while((task = ui_thread_tasks.pop_if_available()) != std::nullopt) { + task.value()(); + } + } + std::unique_ptr<MatrixEvent> Matrix::pop_event() { std::lock_guard<std::mutex> lock(event_queue_mutex); if(!current_event_queue_room || event_queue.empty()) |