diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/QuickMedia.cpp | 167 | ||||
-rw-r--r-- | src/plugins/Matrix.cpp | 261 |
2 files changed, 319 insertions, 109 deletions
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index e6d7b9a..ad9cc7f 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -330,6 +330,9 @@ namespace QuickMedia { abort(); } loading_icon.setSmooth(true); + load_sprite.setTexture(loading_icon, true); + sf::Vector2u loading_icon_size = loading_icon.getSize(); + load_sprite.setOrigin(loading_icon_size.x * 0.5f, loading_icon_size.y * 0.5f); struct sigaction action; action.sa_handler = sigpipe_handler; @@ -589,23 +592,15 @@ namespace QuickMedia { if(matrix) { matrix->use_tor = use_tor; - TaskResult task_result = run_task_with_loading_screen([this]() { - return matrix->load_and_verify_cached_session() == PluginResult::OK; - }); - - if(task_result == TaskResult::TRUE) { + if(matrix->load_cached_session() == PluginResult::OK) { current_page = PageType::CHAT; - } else if(task_result == TaskResult::FALSE) { + } else { fprintf(stderr, "Failed to load session cache, redirecting to login page\n"); current_page = PageType::CHAT_LOGIN; chat_login_page(); - } else { - exit(exit_code); } after_matrix_login_page(); - exit(exit_code); // Exit immediately without waiting for anything to finish - //matrix->stop_sync(); } return exit_code; @@ -1329,6 +1324,12 @@ namespace QuickMedia { window.draw(tab_associated_data[selected_tab].search_result_text); } + if(matrix && !matrix->is_initial_sync_finished()) { + load_sprite.setPosition(body_pos.x + body_size.x * 0.5f, body_pos.y + body_size.y * 0.5f); + load_sprite.setRotation(load_sprite_timer.getElapsedTime().asSeconds() * 400.0); + window.draw(load_sprite); + } + window.display(); if(go_to_previous_page) { @@ -1516,13 +1517,8 @@ namespace QuickMedia { promise.set_value(callback()); }, std::move(result_promise), std::move(callback)); - sf::Sprite load_sprite(loading_icon); - sf::Vector2u loading_icon_size = loading_icon.getSize(); - load_sprite.setOrigin(loading_icon_size.x * 0.5f, loading_icon_size.y * 0.5f); - window_size.x = window.getSize().x; window_size.y = window.getSize().y; - sf::Clock timer; sf::Event event; while(window.isOpen()) { while(window.pollEvent(event)) { @@ -1555,7 +1551,7 @@ namespace QuickMedia { window.clear(back_color); load_sprite.setPosition(window_size.x * 0.5f, window_size.y * 0.5f); - load_sprite.setRotation(timer.getElapsedTime().asSeconds() * 400.0); + load_sprite.setRotation(load_sprite_timer.getElapsedTime().asSeconds() * 400.0); window.draw(load_sprite); window.display(); } @@ -3073,7 +3069,7 @@ namespace QuickMedia { Message *message = nullptr; }; - void Program::chat_page(MatrixChatPage *chat_page, RoomData *current_room) { + void Program::chat_page(MatrixChatPage *matrix_chat_page, RoomData *current_room) { assert(strcmp(plugin_name, "matrix") == 0); auto video_page = std::make_unique<MatrixVideoPage>(this); @@ -3341,20 +3337,18 @@ namespace QuickMedia { }; AsyncTask<Messages> previous_messages_future; - bool fetching_previous_messages_running = false; RoomData *previous_messages_future_room = nullptr; //const int num_fetch_message_threads = 4; AsyncTask<std::shared_ptr<Message>> fetch_message_future; - bool fetching_message_running = false; RoomData *fetch_future_room = nullptr; BodyItem *fetch_body_item = nullptr; int fetch_message_tab = -1; // TODO: How about instead fetching all messages we have, not only the visible ones? also fetch with multiple threads. // TODO: Cancel when going to another room? - tabs[PINNED_TAB_INDEX].body->body_item_render_callback = [this, ¤t_room, &fetch_message_future, &tabs, &find_body_item_by_event_id, &fetching_message_running, &fetch_future_room, &fetch_body_item, &fetch_message_tab](BodyItem *body_item) { - if(fetching_message_running || !current_room) + tabs[PINNED_TAB_INDEX].body->body_item_render_callback = [this, ¤t_room, &fetch_message_future, &tabs, &find_body_item_by_event_id, &fetch_future_room, &fetch_body_item, &fetch_message_tab](BodyItem *body_item) { + if(fetch_message_future.valid() || !current_room) return; PinnedEventData *event_data = static_cast<PinnedEventData*>(body_item->userdata); @@ -3371,7 +3365,6 @@ namespace QuickMedia { return; } - fetching_message_running = true; std::string message_event_id = event_data->event_id; fetch_future_room = current_room; fetch_body_item = body_item; @@ -3385,8 +3378,8 @@ namespace QuickMedia { // TODO: How about instead fetching all messages we have, not only the visible ones? also fetch with multiple threads. // TODO: Cancel when going to another room? - tabs[MESSAGES_TAB_INDEX].body->body_item_render_callback = [this, ¤t_room, &fetch_message_future, &tabs, &find_body_item_by_event_id, &fetching_message_running, &fetch_future_room, &fetch_body_item, &fetch_message_tab](BodyItem *body_item) { - if(fetching_message_running || !current_room) + tabs[MESSAGES_TAB_INDEX].body->body_item_render_callback = [this, ¤t_room, &fetch_message_future, &tabs, &find_body_item_by_event_id, &fetch_future_room, &fetch_body_item, &fetch_message_tab](BodyItem *body_item) { + if(fetch_message_future.valid() || !current_room) return; Message *message = static_cast<Message*>(body_item->userdata); @@ -3402,7 +3395,6 @@ namespace QuickMedia { return; } - fetching_message_running = true; std::string message_event_id = message->related_event_id; fetch_future_room = current_room; fetch_body_item = body_item; @@ -3565,6 +3557,28 @@ namespace QuickMedia { return false; }; + auto cleanup_tasks = [&set_read_marker_future, &previous_messages_future, &fetch_message_future, &typing_state_queue, &typing_state_thread, &post_task_queue, &post_thread, &unreferenced_event_by_room, &tabs]() { + set_read_marker_future.cancel(); + previous_messages_future.cancel(); + fetch_message_future.cancel(); + typing_state_queue.close(); + program_kill_in_thread(typing_state_thread.get_id()); + if(typing_state_thread.joinable()) + typing_state_thread.join(); + post_task_queue.close(); + program_kill_in_thread(post_thread.get_id()); + if(post_thread.joinable()) + post_thread.join(); + + unreferenced_event_by_room.clear(); + + for(auto &body_item : tabs[PINNED_TAB_INDEX].body->items) { + delete (PinnedEventData*)body_item->userdata; + } + + tabs.clear(); + }; + float tab_shade_height = 0.0f; bool frame_skip_text_entry = false; @@ -3600,9 +3614,8 @@ namespace QuickMedia { hit_top = false; break; } - if(hit_top && !fetching_previous_messages_running && selected_tab == MESSAGES_TAB_INDEX && current_room) { + if(hit_top && !previous_messages_future.valid() && selected_tab == MESSAGES_TAB_INDEX && current_room) { gradient_inc = 0; - fetching_previous_messages_running = true; previous_messages_future_room = current_room; previous_messages_future = [this, &previous_messages_future_room]() { Messages messages; @@ -3809,7 +3822,7 @@ namespace QuickMedia { } frame_skip_text_entry = false; - chat_page->update(); + matrix_chat_page->update(); switch(new_page) { case PageType::FILE_MANAGER: { @@ -3853,26 +3866,11 @@ namespace QuickMedia { break; } case PageType::CHAT_LOGIN: { - set_read_marker_future.cancel(); - previous_messages_future.cancel(); - fetch_message_future.cancel(); - typing_state_queue.close(); - program_kill_in_thread(typing_state_thread.get_id()); - if(typing_state_thread.joinable()) - typing_state_thread.join(); - post_task_queue.close(); - program_kill_in_thread(post_thread.get_id()); - if(post_thread.joinable()) - post_thread.join(); + cleanup_tasks(); new_page = PageType::CHAT; matrix->stop_sync(); matrix->logout(); - for(ChatTab &tab : tabs) { - tab.body->clear_cache(); - } // TODO: Instead of doing this, exit this current function and navigate to chat login page instead. - // This doesn't currently work because at the end of this function there are futures that need to wait - // and one of them is /sync, which has a timeout of 30 seconds. That timeout has to be killed somehow. //delete current_plugin; //current_plugin = new Matrix(); current_page = PageType::CHAT_LOGIN; @@ -3974,7 +3972,7 @@ namespace QuickMedia { setting_read_marker = false; } - if(fetching_previous_messages_running && previous_messages_future.ready()) { + if(previous_messages_future.ready()) { Messages new_messages = previous_messages_future.get(); fprintf(stderr, "Finished fetching older messages, num new messages: %zu\n", new_messages.size()); // Ignore finished fetch of messages if it happened in another room. When we navigate back to the room we will get the messages again @@ -3992,10 +3990,9 @@ namespace QuickMedia { modify_related_messages_in_current_room(new_messages); resolve_unreferenced_events_with_body_items(tabs[MESSAGES_TAB_INDEX].body->items.data(), num_new_body_items); } - fetching_previous_messages_running = false; } - if(fetching_message_running && fetch_message_future.ready()) { + if(fetch_message_future.ready()) { std::shared_ptr<Message> message = fetch_message_future.get(); fprintf(stderr, "Finished fetching message: %s\n", message ? message->event_id.c_str() : "(null)"); // Ignore finished fetch of messages if it happened in another room. When we navigate back to the room we will get the messages again @@ -4020,7 +4017,6 @@ namespace QuickMedia { } } } - fetching_message_running = false; fetch_message_tab = -1; } @@ -4079,7 +4075,7 @@ namespace QuickMedia { } // TODO: Have one for each room. Also add bottom one? for fetching new messages (currently not implemented, is it needed?) - if(fetching_previous_messages_running && selected_tab == MESSAGES_TAB_INDEX) { + if(previous_messages_future.valid() && selected_tab == MESSAGES_TAB_INDEX) { double progress = 0.5 + std::sin(std::fmod(gradient_inc, 360.0) * 0.017453292519943295 - 1.5707963267948966*0.5) * 0.5; gradient_inc += (frame_time_ms * 0.5); sf::Color top_color = interpolate_colors(back_color, sf::Color(175, 180, 188), progress); @@ -4135,7 +4131,7 @@ namespace QuickMedia { } } - if(selected_tab == MESSAGES_TAB_INDEX && current_room) { + if(selected_tab == MESSAGES_TAB_INDEX && current_room && matrix->is_initial_sync_finished()) { BodyItem *last_visible_item = tabs[selected_tab].body->get_last_fully_visible_item(); if(is_window_focused && chat_state != ChatState::URL_SELECTION && current_room && last_visible_item && !setting_read_marker && read_marker_timer.getElapsedTime().asMilliseconds() >= read_marker_timeout_ms) { Message *message = (Message*)last_visible_item->userdata; @@ -4159,8 +4155,31 @@ namespace QuickMedia { window.draw(logo_sprite); } + if(matrix && !matrix->is_initial_sync_finished()) { + load_sprite.setPosition(body_pos.x + body_size.x * 0.5f, body_pos.y + body_size.y * 0.5f); + load_sprite.setRotation(load_sprite_timer.getElapsedTime().asSeconds() * 400.0); + window.draw(load_sprite); + } + window.display(); + if(matrix_chat_page->should_clear_data) { + matrix_chat_page->should_clear_data = false; + cleanup_tasks(); + + while(!matrix->is_initial_sync_finished()) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + + current_room = matrix->get_room_by_id(current_room->id); + if(current_room) { + chat_page(matrix_chat_page, current_room); + return; + } else { + go_to_previous_page = true; + } + } + if(go_to_previous_page) { go_to_previous_page = false; goto chat_page_end; @@ -4168,21 +4187,7 @@ namespace QuickMedia { } chat_page_end: - set_read_marker_future.cancel(); - previous_messages_future.cancel(); - fetch_message_future.cancel(); - typing_state_queue.close(); - program_kill_in_thread(typing_state_thread.get_id()); - if(typing_state_thread.joinable()) - typing_state_thread.join(); - post_task_queue.close(); - program_kill_in_thread(post_thread.get_id()); - if(post_thread.joinable()) - post_thread.join(); - - for(auto &body_item : tabs[PINNED_TAB_INDEX].body->items) { - delete (PinnedEventData*)body_item->userdata; - } + cleanup_tasks(); } void Program::after_matrix_login_page() { @@ -4209,13 +4214,27 @@ namespace QuickMedia { 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(invites_body), std::move(matrix_invites_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); - sf::Sprite load_sprite(loading_icon); - sf::Vector2u loading_icon_size = loading_icon.getSize(); - load_sprite.setOrigin(loading_icon_size.x * 0.5f, loading_icon_size.y * 0.5f); - - sf::Clock timer; sf::Event event; - while(window.isOpen() && !matrix->is_initial_sync_finished()) { + std::string sync_err_msg; +#if 0 + while(window.isOpen()) { + if(matrix->is_initial_sync_finished()) + break; + else if(matrix->did_initial_sync_fail(sync_err_msg)) { + show_notification("QuickMedia", "Initial sync failed, reason: " + sync_err_msg, Urgency::CRITICAL); + matrix->stop_sync(); + matrix->logout(); + // TODO: Instead of doing this, exit this current function and navigate to chat login page instead. + //delete current_plugin; + //current_plugin = new Matrix(); + current_page = PageType::CHAT_LOGIN; + chat_login_page(); + if(current_page == PageType::CHAT) + after_matrix_login_page(); + exit(0); + break; + } + while(window.pollEvent(event)) { if(event.type == sf::Event::Closed) window.close(); @@ -4228,15 +4247,17 @@ namespace QuickMedia { window.close(); } } + window.clear(back_color); load_sprite.setPosition(window_size.x * 0.5f, window_size.y * 0.5f); - load_sprite.setRotation(timer.getElapsedTime().asSeconds() * 400.0); + load_sprite.setRotation(load_sprite_timer.getElapsedTime().asSeconds() * 400.0); window.draw(load_sprite); window.display(); } - +#endif while(window.isOpen()) { page_loop(tabs); } + matrix->stop_sync(); } } diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index 22c2447..de33c3c 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -7,6 +7,8 @@ #include <rapidjson/document.h> #include <rapidjson/writer.h> #include <rapidjson/stringbuffer.h> +#include <rapidjson/filereadstream.h> +#include <rapidjson/filewritestream.h> #include <fcntl.h> #include <unistd.h> #include "../../include/QuickMedia.hpp" @@ -230,6 +232,7 @@ namespace QuickMedia { } void MatrixQuickMedia::join_room(RoomData *room) { + std::lock_guard<std::mutex> lock(room_body_items_mutex); std::string room_name = room->get_name(); if(room_name.empty()) room_name = room->id; @@ -246,6 +249,7 @@ namespace QuickMedia { } 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); @@ -254,10 +258,12 @@ 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); } 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); } @@ -317,6 +323,16 @@ namespace QuickMedia { update_pending_room_messages(page_type); } + 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(); + room_tags_page->clear_data(); + invites_page->clear_data(); + } + void MatrixQuickMedia::update_pending_room_messages(MatrixPageType page_type) { std::lock_guard<std::mutex> lock(pending_room_messages_mutex); bool is_window_focused = program->is_window_focused(); @@ -405,6 +421,11 @@ namespace QuickMedia { void MatrixRoomsPage::update() { { std::lock_guard<std::mutex> lock(mutex); + if(clear_data_on_update) { + clear_data_on_update = false; + body->clear_items(); + } + 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 @@ -414,6 +435,7 @@ namespace QuickMedia { current_chat_page = nullptr; } } + pending_remove_body_items.clear(); body->clamp_selection(); body->append_items(std::move(room_body_items)); @@ -459,6 +481,15 @@ namespace QuickMedia { 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; + if(current_chat_page) + current_chat_page->should_clear_data = 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); @@ -477,6 +508,11 @@ namespace QuickMedia { void MatrixRoomTagsPage::update() { { std::lock_guard<std::recursive_mutex> lock(mutex); + 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()) @@ -558,6 +594,16 @@ namespace QuickMedia { 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; + if(current_rooms_page) + current_rooms_page->clear_data(); + } + MatrixInvitesPage::MatrixInvitesPage(Program *program, Matrix *matrix, Body *body) : Page(program), matrix(matrix), body(body) { } @@ -597,6 +643,11 @@ namespace QuickMedia { void MatrixInvitesPage::update() { std::lock_guard<std::mutex> lock(mutex); + if(clear_data_on_update) { + clear_data_on_update = false; + body->clear_items(); + } + for(const std::string &room_id : pending_remove_body_items) { remove_body_item_by_url(body->items, room_id); } @@ -621,6 +672,15 @@ namespace QuickMedia { pending_remove_body_items.push_back(room_id); } + void MatrixInvitesPage::clear_data() { + std::lock_guard<std::mutex> lock(mutex); + body_items.clear(); + pending_remove_body_items.clear(); + title = "Invites (0)"; + prev_invite_count = 0; + clear_data_on_update = true; + } + MatrixChatPage::MatrixChatPage(Program *program, std::string room_id, MatrixRoomsPage *rooms_page) : Page(program), room_id(std::move(room_id)), rooms_page(rooms_page) { if(rooms_page) rooms_page->set_current_chat_page(this); @@ -637,6 +697,66 @@ namespace QuickMedia { rooms_page->update(); } + static std::array<const char*, 7> sync_fail_error_codes = { + "M_FORBIDDEN", + "M_UNKNOWN_TOKEN", + "M_MISSING_TOKEN", + "M_UNAUTHORIZED", + "M_USER_DEACTIVATED", + "M_CAPTCHA_NEEDED", + "M_MISSING_PARAM" + }; + + static void remove_empty_fields_in_sync_rooms_response(rapidjson::Value &rooms_json) { + for(const char *member_name : {"join", "invite", "leave"}) { + auto join_it = rooms_json.FindMember(member_name); + if(join_it != rooms_json.MemberEnd() && join_it->value.IsObject() && join_it->value.MemberCount() == 0) + rooms_json.RemoveMember(join_it); + } + } + + static void remove_ephemeral_field_in_sync_rooms_response(rapidjson::Value &rooms_json) { + auto join_it = rooms_json.FindMember("join"); + if(join_it == rooms_json.MemberEnd() || !join_it->value.IsObject()) + return; + + for(auto &it : join_it->value.GetObject()) { + if(!it.value.IsObject()) + continue; + it.value.RemoveMember("ephemeral"); + } + } + + static void remove_empty_fields_in_sync_account_data_response(rapidjson::Value &account_data_json) { + for(const char *member_name : {"events"}) { + auto join_it = account_data_json.FindMember(member_name); + if(join_it != account_data_json.MemberEnd() && join_it->value.IsObject() && join_it->value.MemberCount() == 0) + account_data_json.RemoveMember(join_it); + } + } + + static void remove_unused_sync_data_fields(rapidjson::Value &json_root) { + for(auto it = json_root.MemberBegin(); it != json_root.MemberEnd();) { + if(strcmp(it->name.GetString(), "account_data") == 0 && it->value.IsObject()) { + remove_empty_fields_in_sync_account_data_response(it->value); + if(it->value.MemberCount() == 0) + it = json_root.RemoveMember(it); + else + ++it; + } else if(strcmp(it->name.GetString(), "rooms") == 0 && it->value.IsObject()) { + // TODO: Call this, but dont remove our read marker (needed for notifications on mentions for example). Or maybe we can get it from "account_data"? + //remove_ephemeral_field_in_sync_rooms_response(rooms_it->value); + remove_empty_fields_in_sync_rooms_response(it->value); + if(it->value.MemberCount() == 0) + it = json_root.RemoveMember(it); + else + ++it; + } else { + it = json_root.EraseMember(it); + } + } + } + void Matrix::start_sync(MatrixDelegate *delegate) { if(sync_running) return; @@ -644,10 +764,31 @@ namespace QuickMedia { assert(!this->delegate); this->delegate = delegate; + Path matrix_cache_dir = get_cache_dir().join("matrix"); + if(create_directory_recursive(matrix_cache_dir) != 0) + fprintf(stderr, "Failed to create matrix cache directory\n"); + matrix_cache_dir.join("sync_data.json"); + sync_running = true; - sync_thread = std::thread([this, delegate]() { + sync_thread = std::thread([this, delegate, matrix_cache_dir]() { + FILE *sync_cache_file = fopen(matrix_cache_dir.data.c_str(), "rb"); + if(sync_cache_file) { + rapidjson::Document doc; + char read_buffer[8192]; + rapidjson::FileReadStream is(sync_cache_file, read_buffer, sizeof(read_buffer)); + while(true) { + rapidjson::ParseResult parse_result = doc.ParseStream<rapidjson::kParseStopWhenDoneFlag>(is); + if(parse_result.IsError()) + break; + if(parse_sync_response(doc, delegate) != PluginResult::OK) + fprintf(stderr, "Failed to parse cached sync response\n"); + } + fclose(sync_cache_file); + } + const rapidjson::Value *next_batch_json; PluginResult result; + bool initial_sync = true; while(sync_running) { std::vector<CommandArg> additional_args = { { "-H", "Authorization: Bearer " + access_token }, @@ -661,12 +802,32 @@ namespace QuickMedia { snprintf(url, sizeof(url), "%s/_matrix/client/r0/sync?timeout=30000&since=%s", homeserver.c_str(), next_batch.c_str()); rapidjson::Document json_root; - DownloadResult download_result = download_json(json_root, url, std::move(additional_args), true); + std::string err_msg; + DownloadResult download_result = download_json(json_root, url, std::move(additional_args), true, &err_msg); if(download_result != DownloadResult::OK) { - fprintf(stderr, "Fetch response failed\n"); + fprintf(stderr, "/sync failed\n"); + if(initial_sync && json_root.IsObject()) { + const rapidjson::Value &errcode_json = GetMember(json_root, "errcode"); + if(errcode_json.IsString()) { + for(const char *sync_fail_error_code : sync_fail_error_codes) { + if(strcmp(errcode_json.GetString(), sync_fail_error_code) == 0) { + sync_fail_reason = sync_fail_error_code; + const rapidjson::Value &error_json = GetMember(json_root, "error"); + if(error_json.IsString()) + sync_fail_reason = error_json.GetString(); + sync_failed = true; + sync_running = false; + break; + } + } + } + } goto sync_end; } + if(next_batch.empty()) + clear_sync_cache_for_new_sync(); + result = parse_sync_response(json_root, delegate); if(result != PluginResult::OK) { fprintf(stderr, "Failed to parse sync response\n"); @@ -675,15 +836,31 @@ namespace QuickMedia { next_batch_json = &GetMember(json_root, "next_batch"); if(next_batch_json->IsString()) { - next_batch = next_batch_json->GetString(); + set_next_batch(next_batch_json->GetString()); fprintf(stderr, "Matrix: next batch: %s\n", next_batch.c_str()); } else { + set_next_batch("Invalid"); fprintf(stderr, "Matrix: missing next batch\n"); } sync_end: if(sync_running) std::this_thread::sleep_for(std::chrono::seconds(1)); + + if(!json_root.IsObject()) + continue; + + // TODO: Circulate file + FILE *sync_cache_file = fopen(matrix_cache_dir.data.c_str(), initial_sync ? "wb" : "ab"); + initial_sync = false; + if(sync_cache_file) { + char buffer[4096]; + rapidjson::FileWriteStream file_write_stream(sync_cache_file, buffer, sizeof(buffer)); + rapidjson::Writer<rapidjson::FileWriteStream> writer(file_write_stream); + remove_unused_sync_data_fields(json_root); + json_root.Accept(writer); + fclose(sync_cache_file); + } } }); } @@ -694,12 +871,23 @@ namespace QuickMedia { if(sync_thread.joinable()) sync_thread.join(); delegate = nullptr; + sync_failed = false; + sync_fail_reason.clear(); } bool Matrix::is_initial_sync_finished() const { return !next_batch.empty(); } + bool Matrix::did_initial_sync_fail(std::string &err_msg) { + if(sync_failed) { + err_msg = sync_fail_reason; + return true; + } else { + return false; + } + } + void Matrix::get_room_sync_data(RoomData *room, SyncData &sync_data) { room->acquire_room_lock(); auto &room_messages = room->get_messages_thread_unsafe(); @@ -1692,7 +1880,7 @@ namespace QuickMedia { if(from.empty()) { fprintf(stderr, "Info: missing previous batch for room: %s, using /sync next batch\n", room_data->id.c_str()); // TODO: When caching /sync, remember to add lock around getting next_batch! - from = next_batch; + from = get_next_batch(); if(from.empty()) { fprintf(stderr, "Error: missing next batch!\n"); return PluginResult::OK; @@ -2105,22 +2293,13 @@ namespace QuickMedia { auto fetched_message_it = room->fetched_messages_by_event_id.find(event_id); if(fetched_message_it != room->fetched_messages_by_event_id.end()) return fetched_message_it->second; - - rapidjson::Document request_data(rapidjson::kObjectType); - request_data.AddMember("lazy_load_members", true, request_data.GetAllocator()); - - rapidjson::StringBuffer buffer; - rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); - request_data.Accept(writer); std::vector<CommandArg> additional_args = { { "-H", "Authorization: Bearer " + access_token } }; - std::string filter = url_param_encode(buffer.GetString()); - char url[512]; - snprintf(url, sizeof(url), "%s/_matrix/client/r0/rooms/%s/context/%s?limit=0&filter=%s", homeserver.c_str(), room->id.c_str(), event_id.c_str(), filter.c_str()); + snprintf(url, sizeof(url), "%s/_matrix/client/r0/rooms/%s/event/%s", homeserver.c_str(), room->id.c_str(), event_id.c_str()); std::string err_msg; rapidjson::Document json_root; @@ -2136,8 +2315,7 @@ namespace QuickMedia { } } - const rapidjson::Value &event_json = GetMember(json_root, "event"); - std::shared_ptr<Message> new_message = parse_message_event(event_json, room); + std::shared_ptr<Message> new_message = parse_message_event(json_root, room); room->fetched_messages_by_event_id.insert(std::make_pair(event_id, new_message)); return new_message; } @@ -2282,6 +2460,9 @@ namespace QuickMedia { DownloadResult download_result = download_json(json_root, homeserver + "/_matrix/client/r0/login", std::move(additional_args), true, &err_msg); if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result); + Path matrix_sync_data_path = get_cache_dir().join("matrix").join("sync_data.json"); + remove(matrix_sync_data_path.data.c_str()); + if(!json_root.IsObject()) { err_msg = "Failed to parse matrix login response"; return PluginResult::ERR; @@ -2310,7 +2491,6 @@ namespace QuickMedia { json_root.AddMember("homeserver", rapidjson::StringRef(homeserver.c_str()), request_data.GetAllocator()); this->user_id = user_id_json.GetString(); - this->username = extract_user_name_from_user_id(this->user_id); this->access_token = access_token_json.GetString(); this->homeserver = homeserver; @@ -2330,6 +2510,7 @@ namespace QuickMedia { } PluginResult Matrix::logout() { + assert(!sync_running); Path session_path = get_storage_dir().join(SERVICE_NAME).join("session.json"); remove(session_path.data.c_str()); @@ -2345,11 +2526,10 @@ namespace QuickMedia { rooms.clear(); room_data_by_id.clear(); user_id.clear(); - username.clear(); access_token.clear(); homeserver.clear(); upload_limit.reset(); - next_batch.clear(); + set_next_batch(""); invites.clear(); return PluginResult::OK; @@ -2403,7 +2583,7 @@ namespace QuickMedia { return PluginResult::OK; } - PluginResult Matrix::load_and_verify_cached_session() { + PluginResult Matrix::load_cached_session() { Path session_path = get_storage_dir().join(SERVICE_NAME).join("session.json"); std::string session_json_content; if(file_get_content(session_path, session_json_content) != 0) { @@ -2443,20 +2623,7 @@ namespace QuickMedia { std::string access_token = access_token_json.GetString(); std::string homeserver = homeserver_json.GetString(); - std::vector<CommandArg> additional_args = { - { "-H", "Authorization: Bearer " + access_token } - }; - - std::string server_response; - // We want to make any request to the server that can verify that our token is still valid, doesn't matter which call - DownloadResult download_result = download_to_string(homeserver + "/_matrix/client/r0/account/whoami", server_response, std::move(additional_args), use_tor, true); - if(download_result != DownloadResult::OK) { - fprintf(stderr, "Matrix whoami response: %s\n", server_response.c_str()); - return download_result_to_plugin_result(download_result); - } - this->user_id = std::move(user_id); - this->username = extract_user_name_from_user_id(this->user_id); this->access_token = std::move(access_token); this->homeserver = std::move(homeserver); return PluginResult::OK; @@ -2582,7 +2749,7 @@ namespace QuickMedia { DownloadResult download_result = download_to_string(homeserver + "/_matrix/client/r0/rooms/" + room_id + "/leave", server_response, std::move(additional_args), use_tor, true); if(download_result == DownloadResult::OK) { RoomData *room = get_room_by_id(room_id); - if(!room) { + if(room) { delegate->leave_room(room, LeaveType::LEAVE, ""); remove_room(room_id); } @@ -2678,9 +2845,31 @@ namespace QuickMedia { return false; } + void Matrix::set_next_batch(std::string new_next_batch) { + std::lock_guard<std::mutex> lock(next_batch_mutex); + next_batch = std::move(new_next_batch); + } + + std::string Matrix::get_next_batch() { + std::lock_guard<std::mutex> lock(next_batch_mutex); + return next_batch; + } + + void Matrix::clear_sync_cache_for_new_sync() { + std::lock_guard<std::recursive_mutex> room_data_lock(room_data_mutex); + std::lock_guard<std::mutex> invites_lock(invite_mutex); + for(auto &room : rooms) { + room->clear_data(); + } + // 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(); + } + DownloadResult Matrix::download_json(rapidjson::Document &result, const std::string &url, std::vector<CommandArg> additional_args, bool use_browser_useragent, std::string *err_msg) const { if(download_to_json(url, result, std::move(additional_args), use_tor, use_browser_useragent, err_msg == nullptr) != DownloadResult::OK) { - // Cant get error since we parse directory to json. TODO: Make this work somehow? + // Cant get error since we parse directly to json. TODO: Make this work somehow? if(err_msg) *err_msg = "Failed to download/parse json"; return DownloadResult::NET_ERR; |