diff options
author | dec05eba <dec05eba@protonmail.com> | 2022-12-09 23:43:12 +0100 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2022-12-09 23:43:12 +0100 |
commit | 17f74130a93d5b5eb9fb33e453670e7247494104 (patch) | |
tree | 37571bbb0bdb4e5dd31e57bd5dc710f124515d06 /src/plugins | |
parent | 49e8081794b50a2b05d9d3b78860e34659ade3fc (diff) |
Matrix: continue from cache instead of initial sync everytime. Mainly improves pantalaimon sync speed
Diffstat (limited to 'src/plugins')
-rw-r--r-- | src/plugins/Matrix.cpp | 223 | ||||
-rw-r--r-- | src/plugins/utils/UniqueProcess.cpp | 113 |
2 files changed, 175 insertions, 161 deletions
diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index 0d93bd0..11eca06 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -17,6 +17,7 @@ #include <rapidjson/filewritestream.h> #include <fcntl.h> #include <unistd.h> +#include <fstream> #include <malloc.h> #include "../../include/QuickMedia.hpp" #include <HtmlParser.h> @@ -38,7 +39,6 @@ namespace QuickMedia { // then we cant see room message preview. TODO: Fix this somehow. // TODO: What about state events in initial sync in timeline? such as user display name change. static const char* INITIAL_FILTER = "{\"presence\":{\"limit\":0,\"types\":[\"\"]},\"account_data\":{\"types\":[\"qm.emoji\",\"m.direct\"]},\"room\":{\"state\":{\"not_types\":[\"m.room.related_groups\",\"m.room.power_levels\",\"m.room.join_rules\",\"m.room.history_visibility\",\"m.room.canonical_alias\",\"m.space.child\"],\"lazy_load_members\":true},\"timeline\":{\"types\":[\"m.room.message\"],\"limit\":1,\"lazy_load_members\":true},\"ephemeral\":{\"limit\":0,\"types\":[\"\"],\"lazy_load_members\":true},\"account_data\":{\"limit\":1,\"types\":[\"m.fully_read\",\"m.tag\",\"qm.last_read_message_timestamp\"],\"lazy_load_members\":true}}}"; - static const char* ADDITIONAL_MESSAGES_FILTER = "{\"presence\":{\"types\":[\"\"]},\"account_data\":{\"limit\":0,\"types\":[\"\"]},\"room\":{\"state\":{\"not_types\":[\"m.room.related_groups\",\"m.room.power_levels\",\"m.room.join_rules\",\"m.room.history_visibility\",\"m.room.canonical_alias\",\"m.space.child\"],\"lazy_load_members\":true},\"timeline\":{\"limit\":20,\"lazy_load_members\":true},\"ephemeral\":{\"limit\":0,\"types\":[\"\"],\"lazy_load_members\":true},\"account_data\":{\"limit\":0,\"types\":[\"\"],\"lazy_load_members\":true}}}"; static const char* CONTINUE_FILTER = "{\"presence\":{\"limit\":0,\"types\":[\"\"]},\"account_data\":{\"types\":[\"qm.emoji\",\"m.direct\"]},\"room\":{\"state\":{\"not_types\":[\"m.room.related_groups\",\"m.room.power_levels\",\"m.room.join_rules\",\"m.room.history_visibility\",\"m.room.canonical_alias\",\"m.space.child\"],\"lazy_load_members\":true},\"timeline\":{\"lazy_load_members\":true},\"ephemeral\":{\"limit\":0,\"types\":[\"\"],\"lazy_load_members\":true},\"account_data\":{\"types\":[\"m.fully_read\",\"m.tag\",\"qm.last_read_message_timestamp\"],\"lazy_load_members\":true}}}"; static bool is_gpg_installed = false; @@ -802,15 +802,6 @@ namespace QuickMedia { body->set_selected_item(found_item_index, false); } - void MatrixQuickMedia::clear_data() { - //room_body_item_by_room.clear(); - //pending_room_messages.clear(); - //rooms_page->clear_data(); - //room_tags_page->clear_data(); - invites_page->clear_data(); - //unread_notifications.clear(); - } - static std::shared_ptr<Message> get_last_message_by_timestamp(const Messages &messages) { if(messages.empty()) return nullptr; @@ -1038,12 +1029,6 @@ namespace QuickMedia { body->select_first_item(); } - void MatrixRoomsPage::clear_data() { - body->clear_items(); - if(current_chat_page) - current_chat_page->should_clear_data = true; - } - PluginResult MatrixRoomTagsPage::submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) { auto body = create_body(true); Body *body_ptr = body.get(); @@ -1131,13 +1116,6 @@ namespace QuickMedia { current_rooms_page = rooms_page; } - void MatrixRoomTagsPage::clear_data() { - tag_body_items_by_name.clear(); - body->clear_items(); - if(current_rooms_page) - current_rooms_page->clear_data(); - } - MatrixInvitesPage::MatrixInvitesPage(Program *program, Matrix *matrix, Body *body) : Page(program), matrix(matrix), body(body) { } @@ -1198,12 +1176,6 @@ namespace QuickMedia { } } - void MatrixInvitesPage::clear_data() { - body->clear_items(); - prev_invite_count = 0; - title = "Invites (0)"; - } - PluginResult MatrixSettingsPage::submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) { if(args.url == "join") { result_tabs.push_back(Tab{create_body(), std::make_unique<MatrixRoomInputPage>(program, matrix), create_search_bar("Enter room id...", SEARCH_DELAY_FILTER)}); @@ -1677,44 +1649,6 @@ namespace QuickMedia { "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_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); - } - } - } - bool Matrix::start_sync(MatrixDelegate *delegate, bool &cached) { cached = true; if(sync_running) @@ -1741,21 +1675,32 @@ namespace QuickMedia { load_custom_emoji_from_cache(); sync_thread = std::thread([this, matrix_cache_dir]() { + FILE *sync_cache_file; + const rapidjson::Value *next_batch_json = nullptr; sync_is_cache = true; - FILE *sync_cache_file = fopen(matrix_cache_dir.data.c_str(), "rb"); - if(sync_cache_file) { + + std::ifstream sync_cache_file_stream; + sync_cache_file_stream.open(matrix_cache_dir.data.c_str(), std::ifstream::in | std::ifstream::binary); + if(sync_cache_file_stream.good()) { 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); + std::string line; + while(std::getline(sync_cache_file_stream, line)) { + rapidjson::ParseResult parse_result = doc.Parse<rapidjson::kParseStopWhenDoneFlag>(line.c_str(), line.size()); if(parse_result.IsError()) - break; - if(parse_sync_response(doc, false, false) != PluginResult::OK) + continue; // This should NEVER happen. Do initial sync if it does and remove cache? :( TODO + + if(parse_sync_response(doc, false) != PluginResult::OK) fprintf(stderr, "Failed to parse cached sync response\n"); + + next_batch_json = &GetMember(doc, "next_batch"); + if(next_batch_json->IsString()) { + set_next_batch(next_batch_json->GetString()); + //fprintf(stderr, "Matrix: next batch: %s\n", next_batch.c_str()); + } } - fclose(sync_cache_file); + malloc_trim(0); } + sync_cache_file_stream.close(); sync_is_cache = false; load_qm_read_markers_from_account_data(); // TODO: Remove when https://github.com/matrix-org/synapse/issues/14444 is fixed, if ever. @@ -1777,16 +1722,22 @@ namespace QuickMedia { else filter = FILTER; #endif - std::string filter_encoded = url_param_encode(INITIAL_FILTER); - std::vector<CommandArg> additional_args = { { "-H", "Authorization: Bearer " + access_token }, { "-m", "35" } }; - const rapidjson::Value *next_batch_json; + next_batch_json = nullptr; PluginResult result; - bool initial_sync = true; + bool initial_sync = next_batch.empty(); + bool first_sync = true; + + std::string filter_encoded; + if(initial_sync) + filter_encoded = url_param_encode(INITIAL_FILTER); + else + filter_encoded = url_param_encode(CONTINUE_FILTER); + while(sync_running) { char url[2048]; if(next_batch.empty()) @@ -1821,10 +1772,7 @@ namespace QuickMedia { } } - if(next_batch.empty()) - clear_sync_cache_for_new_sync(); - - result = parse_sync_response(json_root, false, initial_sync); + result = parse_sync_response(json_root, initial_sync); if(result != PluginResult::OK) { fprintf(stderr, "Failed to parse sync response\n"); initial_sync = false; @@ -1842,7 +1790,10 @@ namespace QuickMedia { goto sync_end; } - if(initial_sync) { + if(first_sync) { + first_sync = false; + filter_encoded = url_param_encode(CONTINUE_FILTER); + notification_thread = std::thread([this]() { get_previous_notifications([this](const MatrixNotification ¬ification) { if(notification.read) @@ -1853,39 +1804,8 @@ namespace QuickMedia { delegate->add_unread_notification(std::move(notification)); }); }); - finished_fetching_notifications = true; - - { - std::vector<CommandArg> additional_args = { - { "-H", "Authorization: Bearer " + access_token }, - { "-m", "35" } - }; - - char url[1024]; - std::string filter_encoded = url_param_encode(ADDITIONAL_MESSAGES_FILTER); - snprintf(url, sizeof(url), "%s/_matrix/client/r0/sync?filter=%s&timeout=0", homeserver.c_str(), filter_encoded.c_str()); - - rapidjson::Document json_root; - std::string err_msg; - DownloadResult download_result = download_json(json_root, url, additional_args, true, &err_msg); - if(download_result != DownloadResult::OK) { - fprintf(stderr, "/sync for additional messages failed\n"); - return; - } - - // TODO: Test? - //if(next_batch.empty()) - // clear_sync_cache_for_new_sync(); - - additional_messages_queue.pop_wait(); - parse_sync_response(json_root, true, false); - } }); - - filter_encoded = url_param_encode(CONTINUE_FILTER); - additional_messages_queue.push(true); - malloc_trim(0); } #if 0 @@ -1895,16 +1815,25 @@ namespace QuickMedia { } #endif - // TODO: Circulate file + // TODO: Use a NoSQL database. + // TODO: Remove very old cache. + // TODO: Find a way to remove this? this makes sync work like other clients but we dont want that! + // If the last sync was long ago then it has to sync ALL messages again. Maybe check if sync file + // is XX days old and then ignore it? sync_cache_file = fopen(matrix_cache_dir.data.c_str(), initial_sync ? "wb" : "ab"); initial_sync = false; if(sync_cache_file) { if(json_root.IsObject()) { - 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); + rapidjson::StringBuffer buffer; + rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); json_root.Accept(writer); + + std::string json_data(buffer.GetString(), buffer.GetSize()); + string_replace_all(json_data, '\n', ' '); + json_data += '\n'; + + fwrite(json_data.data(), 1, json_data.size(), sync_cache_file); + malloc_trim(0); } fclose(sync_cache_file); } @@ -1925,13 +1854,6 @@ namespace QuickMedia { program_kill_in_thread(sync_thread.get_id()); sync_thread.join(); } - - if(sync_additional_messages_thread.joinable()) { - program_kill_in_thread(sync_additional_messages_thread.get_id()); - additional_messages_queue.close(); - sync_additional_messages_thread.join(); - additional_messages_queue.restart(); - } if(notification_thread.joinable()) { program_kill_in_thread(notification_thread.get_id()); @@ -2113,12 +2035,12 @@ namespace QuickMedia { } } - PluginResult Matrix::parse_sync_response(const rapidjson::Document &root, bool is_additional_messages_sync, bool initial_sync) { + PluginResult Matrix::parse_sync_response(const rapidjson::Document &root, bool initial_sync) { if(!root.IsObject()) return PluginResult::ERR; const rapidjson::Value &rooms_json = GetMember(root, "rooms"); - parse_sync_room_data(rooms_json, is_additional_messages_sync, initial_sync); + parse_sync_room_data(rooms_json, initial_sync); const rapidjson::Value &account_data_json = GetMember(root, "account_data"); parse_sync_account_data(account_data_json); @@ -2300,7 +2222,7 @@ namespace QuickMedia { return PluginResult::OK; } - PluginResult Matrix::parse_sync_room_data(const rapidjson::Value &rooms_json, bool is_additional_messages_sync, bool initial_sync) { + PluginResult Matrix::parse_sync_room_data(const rapidjson::Value &rooms_json, bool initial_sync) { if(!rooms_json.IsObject()) return PluginResult::OK; @@ -2342,16 +2264,9 @@ namespace QuickMedia { const rapidjson::Value &timeline_json = GetMember(it.value, "timeline"); if(timeline_json.IsObject()) { - if(is_additional_messages_sync) { - // This may be non-existent if this is the first event in the room - const rapidjson::Value &prev_batch_json = GetMember(timeline_json, "prev_batch"); - if(prev_batch_json.IsString()) - room->set_prev_batch(prev_batch_json.GetString()); - } - bool has_unread_notifications = false; const rapidjson::Value &unread_notification_json = GetMember(it.value, "unread_notifications"); - if(unread_notification_json.IsObject() && !is_additional_messages_sync && !sync_is_cache) { + if(unread_notification_json.IsObject() && !sync_is_cache) { const rapidjson::Value &highlight_count_json = GetMember(unread_notification_json, "highlight_count"); if(highlight_count_json.IsInt64() && (highlight_count_json.GetInt64() > 0 || initial_sync)) { room->unread_notification_count = highlight_count_json.GetInt64(); @@ -2368,7 +2283,7 @@ namespace QuickMedia { if(account_data_json.IsObject()) { const rapidjson::Value &events_json = GetMember(account_data_json, "events"); auto me = get_me(room); - events_set_user_read_marker(events_json, room, me, is_additional_messages_sync); + events_set_user_read_marker(events_json, room, me); } if(is_new_room) @@ -2381,7 +2296,7 @@ namespace QuickMedia { if(account_data_json.IsObject()) { const rapidjson::Value &events_json = GetMember(account_data_json, "events"); auto me = get_me(room); - events_set_user_read_marker(events_json, room, me, is_additional_messages_sync); + events_set_user_read_marker(events_json, room, me); } if(is_new_room) @@ -2410,13 +2325,11 @@ namespace QuickMedia { } } - if(!is_additional_messages_sync) { - const rapidjson::Value &leave_json = GetMember(rooms_json, "leave"); - remove_rooms(leave_json); + const rapidjson::Value &leave_json = GetMember(rooms_json, "leave"); + remove_rooms(leave_json); - const rapidjson::Value &invite_json = GetMember(rooms_json, "invite"); - add_invites(invite_json); - } + const rapidjson::Value &invite_json = GetMember(rooms_json, "invite"); + add_invites(invite_json); if(initial_sync) { std::lock_guard<std::recursive_mutex> lock(room_data_mutex); @@ -2549,7 +2462,7 @@ namespace QuickMedia { return user_info; } - void Matrix::events_set_user_read_marker(const rapidjson::Value &events_json, RoomData *room_data, std::shared_ptr<UserInfo> &me, bool is_additional_messages_sync) { + void Matrix::events_set_user_read_marker(const rapidjson::Value &events_json, RoomData *room_data, std::shared_ptr<UserInfo> &me) { assert(me); // TODO: Remove read marker from user and set it for the room instead. We need that in the matrix pages also if(!events_json.IsArray() || !me) return; @@ -2571,7 +2484,7 @@ namespace QuickMedia { if(!event_id_json.IsString()) continue; - if(!sync_is_cache && !is_additional_messages_sync) + if(!sync_is_cache) room_data->set_user_read_marker(me, std::string(event_id_json.GetString(), event_id_json.GetStringLength())); } else if(strcmp(type_json.GetString(), "qm.last_read_message_timestamp") == 0) { // TODO: Remove qm.last_read_message_timestamp in room level eventually when everybody has data in global level const rapidjson::Value &content_json = GetMember(event_json, "content"); @@ -5887,18 +5800,6 @@ namespace QuickMedia { return next_notifications_token; } - 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(); - 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) { auto user = room->get_user_by_id(user_id); if(user) { diff --git a/src/plugins/utils/UniqueProcess.cpp b/src/plugins/utils/UniqueProcess.cpp new file mode 100644 index 0000000..76b9cb1 --- /dev/null +++ b/src/plugins/utils/UniqueProcess.cpp @@ -0,0 +1,113 @@ +#include "../../../plugins/utils/UniqueProcess.hpp" +#include <stdio.h> +#include <string.h> +#include <limits.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdlib.h> + +namespace QuickMedia { + static bool is_process_running_program(pid_t pid, const char *program_name) { + char filepath[256]; + snprintf(filepath, sizeof(filepath), "/proc/%ld/cmdline", (long)pid); + + int fd = open(filepath, O_RDONLY); + if(fd == -1) + return false; + + char buffer[PATH_MAX + 1]; + ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1); + if(bytes_read == -1) { + close(fd); + return false; + } + buffer[bytes_read] = '\0'; + + char resolved_path[PATH_MAX + 1]; + if(!realpath(buffer, resolved_path)) { + close(fd); + return false; + } + bytes_read = strlen(resolved_path); + resolved_path[bytes_read] = '\0'; + + const char *end = resolved_path + bytes_read; + const char *start = (const char*)memrchr(resolved_path, '/', bytes_read); + if(start) + start += 1; + else + start = buffer; + + const size_t cmd_arg0_len = end - start; + const size_t program_name_len = strlen(program_name); + bool running = (cmd_arg0_len == program_name_len && memcmp(start, program_name, program_name_len) == 0); + close(fd); + return running; + } + + bool is_quickmedia_instance_already_running(const char *pid_file_dir, const char *plugin_name) { + char pid_file[PATH_MAX]; + snprintf(pid_file, sizeof(pid_file), "%s/quickmedia.%s.pid", pid_file_dir, plugin_name); + + char buffer[256]; + int fd = open(pid_file, O_RDONLY); + if(fd == -1) + return false; + + ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1); + if(bytes_read < 0) { + perror("failed to read quickmedia pid file"); + close(fd); + return false; + } + buffer[bytes_read] = '\0'; + close(fd); + + bool running = false; + long pid = 0; + if(sscanf(buffer, "%ld", &pid) == 1) { + if(is_process_running_program(pid, "quickmedia")) { + fprintf(stderr, "Error: quickmedia %s is already running\n", plugin_name); + running = true; + } + } else { + fprintf(stderr, "Warning: quickmedia pid file is in incorrect format, it's possible that its corrupt. Replacing file and continuing...\n"); + running = false; + } + + if(!running) + unlink(pid_file); + + return running; + } + + bool set_quickmedia_instance_unique(const char *pid_file_dir, const char *plugin_name) { + char pid_file[PATH_MAX]; + snprintf(pid_file, sizeof(pid_file), "%s/quickmedia.%s.pid", pid_file_dir, plugin_name); + + int fd = open(pid_file, O_WRONLY|O_CREAT|O_TRUNC, 0777); + if(fd == -1) { + perror("failed to create quickmedia pid file"); + return false; + } + + bool success = true; + char buffer[256]; + const int buffer_size = snprintf(buffer, sizeof(buffer), "%ld", (long)getpid()); + if(write(fd, buffer, buffer_size) == -1) { + perror("failed to write quickmedia pid file"); + success = false; + } + + close(fd); + if(!success) + unlink(pid_file); + return success; + } + + void remove_quickmedia_instance_lock(const char *pid_file_dir, const char *plugin_name) { + char pid_file[PATH_MAX]; + snprintf(pid_file, sizeof(pid_file), "%s/quickmedia.%s.pid", pid_file_dir, plugin_name); + unlink(pid_file); + } +}
\ No newline at end of file |