diff options
Diffstat (limited to 'src/plugins')
-rw-r--r-- | src/plugins/Matrix.cpp | 224 | ||||
-rw-r--r-- | src/plugins/utils/UniqueProcess.cpp | 111 |
2 files changed, 161 insertions, 174 deletions
diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index fca4332..0d93bd0 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -17,7 +17,6 @@ #include <rapidjson/filewritestream.h> #include <fcntl.h> #include <unistd.h> -#include <fstream> #include <malloc.h> #include "../../include/QuickMedia.hpp" #include <HtmlParser.h> @@ -39,6 +38,7 @@ 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,6 +802,15 @@ 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; @@ -1029,6 +1038,12 @@ 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(); @@ -1116,6 +1131,13 @@ 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) { } @@ -1176,6 +1198,12 @@ 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)}); @@ -1649,6 +1677,44 @@ 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) @@ -1675,32 +1741,21 @@ 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; - - 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() && get_file_type(get_cache_dir().join("matrix").join("updated-cache-version")) == FileType::REGULAR) { + FILE *sync_cache_file = fopen(matrix_cache_dir.data.c_str(), "rb"); + if(sync_cache_file) { rapidjson::Document doc; - std::string line; - while(std::getline(sync_cache_file_stream, line)) { - rapidjson::ParseResult parse_result = doc.Parse<rapidjson::kParseStopWhenDoneFlag>(line.c_str(), line.size()); + 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()) - continue; // This should NEVER happen. Do initial sync if it does and remove cache? :( TODO - - if(parse_sync_response(doc, false) != PluginResult::OK) + break; + if(parse_sync_response(doc, false, 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()); - } } - malloc_trim(0); + fclose(sync_cache_file); } - 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. @@ -1722,22 +1777,16 @@ 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" } }; - next_batch_json = nullptr; + const rapidjson::Value *next_batch_json; PluginResult result; - 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); - + bool initial_sync = true; while(sync_running) { char url[2048]; if(next_batch.empty()) @@ -1772,7 +1821,10 @@ namespace QuickMedia { } } - result = parse_sync_response(json_root, initial_sync); + if(next_batch.empty()) + clear_sync_cache_for_new_sync(); + + result = parse_sync_response(json_root, false, initial_sync); if(result != PluginResult::OK) { fprintf(stderr, "Failed to parse sync response\n"); initial_sync = false; @@ -1790,10 +1842,7 @@ namespace QuickMedia { goto sync_end; } - if(first_sync) { - first_sync = false; - filter_encoded = url_param_encode(CONTINUE_FILTER); - + if(initial_sync) { notification_thread = std::thread([this]() { get_previous_notifications([this](const MatrixNotification ¬ification) { if(notification.read) @@ -1804,8 +1853,39 @@ 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 @@ -1815,26 +1895,16 @@ namespace QuickMedia { } #endif - // 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? + // TODO: Circulate file 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()) { - rapidjson::StringBuffer buffer; - rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); + 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); - - 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); - file_overwrite(get_cache_dir().join("matrix").join("updated-cache-version"), "1"); // To make sure the cache format is up to date - malloc_trim(0); } fclose(sync_cache_file); } @@ -1855,6 +1925,13 @@ 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()); @@ -2036,12 +2113,12 @@ namespace QuickMedia { } } - PluginResult Matrix::parse_sync_response(const rapidjson::Document &root, bool initial_sync) { + PluginResult Matrix::parse_sync_response(const rapidjson::Document &root, bool is_additional_messages_sync, bool initial_sync) { if(!root.IsObject()) return PluginResult::ERR; const rapidjson::Value &rooms_json = GetMember(root, "rooms"); - parse_sync_room_data(rooms_json, initial_sync); + parse_sync_room_data(rooms_json, is_additional_messages_sync, initial_sync); const rapidjson::Value &account_data_json = GetMember(root, "account_data"); parse_sync_account_data(account_data_json); @@ -2223,7 +2300,7 @@ namespace QuickMedia { return PluginResult::OK; } - PluginResult Matrix::parse_sync_room_data(const rapidjson::Value &rooms_json, bool initial_sync) { + PluginResult Matrix::parse_sync_room_data(const rapidjson::Value &rooms_json, bool is_additional_messages_sync, bool initial_sync) { if(!rooms_json.IsObject()) return PluginResult::OK; @@ -2265,9 +2342,16 @@ 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() && !sync_is_cache) { + if(unread_notification_json.IsObject() && !is_additional_messages_sync && !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(); @@ -2284,7 +2368,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); + events_set_user_read_marker(events_json, room, me, is_additional_messages_sync); } if(is_new_room) @@ -2297,7 +2381,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); + events_set_user_read_marker(events_json, room, me, is_additional_messages_sync); } if(is_new_room) @@ -2326,11 +2410,13 @@ namespace QuickMedia { } } - const rapidjson::Value &leave_json = GetMember(rooms_json, "leave"); - remove_rooms(leave_json); + if(!is_additional_messages_sync) { + 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); @@ -2463,7 +2549,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) { + 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) { 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; @@ -2485,7 +2571,7 @@ namespace QuickMedia { if(!event_id_json.IsString()) continue; - if(!sync_is_cache) + if(!sync_is_cache && !is_additional_messages_sync) 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"); @@ -5801,6 +5887,18 @@ 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 deleted file mode 100644 index d2025f5..0000000 --- a/src/plugins/utils/UniqueProcess.cpp +++ /dev/null @@ -1,111 +0,0 @@ -#include "../../../plugins/utils/UniqueProcess.hpp" -#include "../../../include/Storage.hpp" -#include <stdio.h> -#include <limits.h> -#include <sys/socket.h> -#include <sys/un.h> -#include <unistd.h> -#include <fcntl.h> -#include <errno.h> - -namespace QuickMedia { - bool is_quickmedia_instance_already_running(const char *sock_file_dir, const char *plugin_name) { - char sock_file[PATH_MAX]; - snprintf(sock_file, sizeof(sock_file), "%s/quickmedia.%s.sock", sock_file_dir, plugin_name); - - std::string resolved_path; - if(file_get_content(sock_file, resolved_path) != 0) - return false; - - resolved_path.resize(108); // sizeof(addr.sun_path) is 108 - - int fd = socket(AF_UNIX, SOCK_STREAM, 0); - if(fd == -1) { - fprintf(stderr, "Error: failed to create unix domain socket, error: %s\n", strerror(errno)); - return true; - } - - fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); - - struct sockaddr_un addr; - memset(&addr, 0, sizeof(addr)); - addr.sun_family = AF_UNIX; - strcpy(addr.sun_path, resolved_path.c_str()); - - bool running = connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == 0; - int err = errno; - if(err == EAGAIN) - running = true; - else if(err == ENOENT) - running = false; - close(fd); - return running; - } - - bool set_quickmedia_instance_unique(const char *sock_file_dir, const char *plugin_name) { - char socket_file[] = "/tmp/quickmedia.XXXXXX"; - int tmp_file_fd = mkstemp(socket_file); - if(tmp_file_fd == -1) { - fprintf(stderr, "Error: failed to create temporary file for unix domain socket, error: %s\n", strerror(errno)); - return false; - } - unlink(socket_file); - close(tmp_file_fd); - - int fd = socket(AF_UNIX, SOCK_STREAM, 0); - if(fd == -1) { - fprintf(stderr, "Error: failed to create unix domain socket, error: %s\n", strerror(errno)); - return false; - } - - struct sockaddr_un addr; - memset(&addr, 0, sizeof(addr)); - addr.sun_family = AF_UNIX; - strcpy(addr.sun_path, socket_file); - - if(bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { - fprintf(stderr, "Error: failed to bind unix domain socket, error: %s\n", strerror(errno)); - unlink(socket_file); - close(fd); - return false; - } - - if(listen(fd, 0) == -1) { - fprintf(stderr, "Error: failed to listen to unix domain socket, error: %s\n", strerror(errno)); - unlink(socket_file); - close(fd); - return false; - } - - char sock_file[PATH_MAX]; - snprintf(sock_file, sizeof(sock_file), "%s/quickmedia.%s.sock", sock_file_dir, plugin_name); - bool success = file_overwrite(sock_file, socket_file) == 0; - if(!success) { - fprintf(stderr, "Error: failed to create %s unix domain socket link file\n", sock_file); - unlink(socket_file); - close(fd); - } - return success; - } - - void remove_quickmedia_instance_lock(const char *sock_file_dir, const char *plugin_name) { - char sock_file[PATH_MAX]; - snprintf(sock_file, sizeof(sock_file), "%s/quickmedia.%s.sock", sock_file_dir, plugin_name); - - std::string resolved_path; - if(file_get_content(sock_file, resolved_path) != 0) { - unlink(sock_file); - return; - } - - resolved_path.resize(108); // sizeof(addr.sun_path) is 108 - - if(resolved_path.size() < 4 || memcmp(resolved_path.data(), "/tmp", 4) != 0) { - unlink(sock_file); - return; - } - - unlink(sock_file); - unlink(resolved_path.c_str()); - } -}
\ No newline at end of file |