aboutsummaryrefslogtreecommitdiff
path: root/src/plugins
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2020-11-03 01:07:57 +0100
committerdec05eba <dec05eba@protonmail.com>2020-11-03 01:07:57 +0100
commit16ca6e63f4fd1b407c826a5574dc20b3f9e71675 (patch)
tree3dbe065229dc130f0a49134bfeeb35ad11d01958 /src/plugins
parent7045953e428076b0f07dc8043a5a1a0ac3d4bec5 (diff)
Matrix: sync with filter, lazy member fetch (reducing sync time from 35 sec with huge server to 3 seconds) and cached fetch to 150ms). Properly show notifications for older messages. Reduce memory usage from 120mb to 13mb
Diffstat (limited to 'src/plugins')
-rw-r--r--src/plugins/Matrix.cpp518
1 files changed, 360 insertions, 158 deletions
diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp
index b7f911f..fd05399 100644
--- a/src/plugins/Matrix.cpp
+++ b/src/plugins/Matrix.cpp
@@ -17,7 +17,6 @@
// Show images/videos inline.
// TODO: Verify if buffer of size 512 is enough for endpoints
// Remove older messages (outside screen) to save memory. Reload them when the selected body item is the top/bottom one.
-// TODO: Use lazy load filter for /sync (filter=0, required GET first to check if its available). If we use filter for sync then we also need to modify Matrix::get_message_by_id to parse state, etc.
static const char* SERVICE_NAME = "matrix";
static const char* OTHERS_ROOM_TAG = "tld.name.others";
@@ -82,7 +81,7 @@ namespace QuickMedia {
}
std::shared_ptr<UserInfo> RoomData::get_user_by_id(const std::string &user_id) {
- std::lock_guard<std::mutex> lock(room_mutex);
+ std::lock_guard<std::recursive_mutex> lock(room_mutex);
auto user_it = user_info_by_user_id.find(user_id);
if(user_it == user_info_by_user_id.end())
return nullptr;
@@ -90,22 +89,22 @@ namespace QuickMedia {
}
void RoomData::add_user(std::shared_ptr<UserInfo> user) {
- std::lock_guard<std::mutex> lock(room_mutex);
+ std::lock_guard<std::recursive_mutex> lock(room_mutex);
user_info_by_user_id.insert(std::make_pair(user->user_id, user));
}
void RoomData::set_user_read_marker(std::shared_ptr<UserInfo> &user, const std::string &event_id) {
- std::lock_guard<std::mutex> lock(user_mutex);
+ std::lock_guard<std::recursive_mutex> lock(user_mutex);
user->read_marker_event_id = event_id;
}
std::string RoomData::get_user_read_marker(std::shared_ptr<UserInfo> &user) {
- std::lock_guard<std::mutex> lock(user_mutex);
+ std::lock_guard<std::recursive_mutex> lock(user_mutex);
return user->read_marker_event_id;
}
void RoomData::prepend_messages_reverse(const std::vector<std::shared_ptr<Message>> &new_messages) {
- std::lock_guard<std::mutex> lock(room_mutex);
+ std::lock_guard<std::recursive_mutex> lock(room_mutex);
for(auto it = new_messages.begin(); it != new_messages.end(); ++it) {
if(message_by_event_id.find((*it)->event_id) == message_by_event_id.end()) {
message_by_event_id.insert(std::make_pair((*it)->event_id, *it));
@@ -115,7 +114,7 @@ namespace QuickMedia {
}
void RoomData::append_messages(const std::vector<std::shared_ptr<Message>> &new_messages) {
- std::lock_guard<std::mutex> lock(room_mutex);
+ std::lock_guard<std::recursive_mutex> lock(room_mutex);
for(auto it = new_messages.begin(); it != new_messages.end(); ++it) {
if(message_by_event_id.find((*it)->event_id) == message_by_event_id.end()) {
message_by_event_id.insert(std::make_pair((*it)->event_id, *it));
@@ -125,7 +124,7 @@ namespace QuickMedia {
}
std::shared_ptr<Message> RoomData::get_message_by_id(const std::string &id) {
- std::lock_guard<std::mutex> lock(room_mutex);
+ std::lock_guard<std::recursive_mutex> lock(room_mutex);
auto message_it = message_by_event_id.find(id);
if(message_it == message_by_event_id.end())
return nullptr;
@@ -133,7 +132,7 @@ namespace QuickMedia {
}
std::vector<std::shared_ptr<UserInfo>> RoomData::get_users_excluding_me(const std::string &my_user_id) {
- std::lock_guard<std::mutex> lock(user_mutex);
+ std::lock_guard<std::recursive_mutex> lock(user_mutex);
std::vector<std::shared_ptr<UserInfo>> users_excluding_me;
for(auto &[user_id, user] : user_info_by_user_id) {
if(user->user_id != my_user_id) {
@@ -160,52 +159,52 @@ namespace QuickMedia {
}
bool RoomData::has_prev_batch() {
- std::lock_guard<std::mutex> lock(room_mutex);
+ std::lock_guard<std::recursive_mutex> lock(room_mutex);
return !prev_batch.empty();
}
void RoomData::set_prev_batch(const std::string &new_prev_batch) {
- std::lock_guard<std::mutex> lock(room_mutex);
+ std::lock_guard<std::recursive_mutex> lock(room_mutex);
prev_batch = new_prev_batch;
}
std::string RoomData::get_prev_batch() {
- std::lock_guard<std::mutex> lock(room_mutex);
+ std::lock_guard<std::recursive_mutex> lock(room_mutex);
return prev_batch;
}
bool RoomData::has_name() {
- std::lock_guard<std::mutex> lock(room_mutex);
+ std::lock_guard<std::recursive_mutex> lock(room_mutex);
return !name.empty();
}
void RoomData::set_name(const std::string &new_name) {
- std::lock_guard<std::mutex> lock(room_mutex);
+ std::lock_guard<std::recursive_mutex> lock(room_mutex);
name = new_name;
}
std::string RoomData::get_name() {
- std::lock_guard<std::mutex> lock(room_mutex);
+ std::lock_guard<std::recursive_mutex> lock(room_mutex);
return name;
}
bool RoomData::has_avatar_url() {
- std::lock_guard<std::mutex> lock(room_mutex);
+ std::lock_guard<std::recursive_mutex> lock(room_mutex);
return !avatar_url.empty();
}
void RoomData::set_avatar_url(const std::string &new_avatar_url) {
- std::lock_guard<std::mutex> lock(room_mutex);
+ std::lock_guard<std::recursive_mutex> lock(room_mutex);
avatar_url = new_avatar_url;
}
std::string RoomData::get_avatar_url() {
- std::lock_guard<std::mutex> lock(room_mutex);
+ std::lock_guard<std::recursive_mutex> lock(room_mutex);
return avatar_url;
}
void RoomData::set_pinned_events(std::vector<std::string> new_pinned_events) {
- std::lock_guard<std::mutex> lock(room_mutex);
+ std::lock_guard<std::recursive_mutex> lock(room_mutex);
pinned_events = std::move(new_pinned_events);
pinned_events_updated = true;
}
@@ -215,10 +214,11 @@ namespace QuickMedia {
}
void RoomData::clear_data() {
- std::lock_guard<std::mutex> lock(room_mutex);
+ std::lock_guard<std::recursive_mutex> lock(room_mutex);
fetched_messages_by_event_id.clear();
user_info_by_user_id.clear();
messages.clear();
+ messages_read_index = 0;
message_by_event_id.clear();
pinned_events.clear();
tags.clear();
@@ -292,6 +292,15 @@ 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));
+ }
+
static int find_top_body_position_for_unread_room(const BodyItems &room_body_items, BodyItem *item_to_swap) {
for(int i = 0; i < (int)room_body_items.size(); ++i) {
const auto &body_item = room_body_items[i];
@@ -304,7 +313,7 @@ namespace QuickMedia {
static int find_top_body_position_for_mentioned_room(const BodyItems &room_body_items, BodyItem *item_to_swap) {
for(int i = 0; i < (int)room_body_items.size(); ++i) {
const auto &body_item = room_body_items[i];
- if(!static_cast<RoomData*>(body_item->userdata)->has_unread_mention || body_item.get() == item_to_swap)
+ if(static_cast<RoomData*>(body_item->userdata)->unread_notification_count == 0 || body_item.get() == item_to_swap)
return i;
}
return -1;
@@ -314,14 +323,30 @@ namespace QuickMedia {
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->has_unread_mention + (int)!room1->last_message_read;
- int room2_focus_sum = (int)room2->has_unread_mention + (int)!room2->last_message_read;
+ 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;
});
}
void MatrixQuickMedia::update(MatrixPageType page_type) {
update_pending_room_messages(page_type);
+ std::lock_guard<std::mutex> room_body_lock(room_body_items_mutex);
+ bool is_window_focused = program->is_window_focused();
+ RoomData *current_room = program->get_current_chat_room();
+ for(auto &it : unread_notifications) {
+ if(!it.second.empty() && (!is_window_focused || it.first != current_room || page_type == MatrixPageType::ROOM_LIST)) {
+ for(auto &unread_notification : it.second) {
+ show_notification("QuickMedia matrix - " + unread_notification.sender + " (" + it.first->get_name() + ")", unread_notification.body);
+ }
+ }
+ update_room_description(it.first, false);
+ }
+ //if(!unread_notifications.empty()) {
+ // rooms_page->sort_rooms();
+ // room_tags_page->sort_rooms();
+ //}
+ unread_notifications.clear();
}
void MatrixQuickMedia::clear_data() {
@@ -332,6 +357,62 @@ namespace QuickMedia {
rooms_page->clear_data();
room_tags_page->clear_data();
invites_page->clear_data();
+ unread_notifications.clear();
+ }
+
+ void MatrixQuickMedia::update_room_description(RoomData *room, bool is_initial_sync) {
+ room->acquire_room_lock();
+ const Messages &messages = room->get_messages_thread_unsafe();
+
+ time_t read_marker_message_timestamp = 0;
+ std::shared_ptr<UserInfo> me = matrix->get_me(room);
+ if(me) {
+ auto read_marker_message = room->get_message_by_id(room->get_user_read_marker(me));
+ if(read_marker_message)
+ read_marker_message_timestamp = read_marker_message->timestamp;
+ }
+
+ // TODO: this wont always work because we dont display all types of messages from server, such as "joined", "left", "kicked", "banned", "changed avatar", "changed display name", etc.
+ // TODO: Binary search?
+ Message *last_unread_message = nullptr;
+ for(auto it = messages.rbegin(), end = messages.rend(); it != end; ++it) {
+ if((*it)->related_event_type != RelatedEventType::EDIT && (*it)->related_event_type != RelatedEventType::REDACTION && (*it)->timestamp > read_marker_message_timestamp) {
+ last_unread_message = (*it).get();
+ break;
+ }
+ }
+ if(!last_unread_message && !messages.empty() && messages.back()->timestamp > read_marker_message_timestamp)
+ last_unread_message = messages.back().get();
+
+ BodyItem *room_body_item = static_cast<BodyItem*>(room->userdata);
+ assert(room_body_item);
+
+ if(last_unread_message) {
+ std::string room_desc = "Unread: " + matrix->message_get_author_displayname(last_unread_message) + ": " + extract_first_line_elipses(last_unread_message->body, 150);
+ int unread_notification_count = room->unread_notification_count;
+ if(unread_notification_count > 0)
+ room_desc += "\n** " + std::to_string(unread_notification_count) + " unread mention(s) **"; // TODO: Better notification?
+ room_body_item->set_description(std::move(room_desc));
+ room_body_item->set_title_color(sf::Color(255, 100, 100));
+ room->last_message_read = false;
+
+ rooms_page->move_room_to_top(room);
+ room_tags_page->move_room_to_top(room);
+ } else if(is_initial_sync) {
+ Message *last_message = nullptr;
+ for(auto it = messages.rbegin(), end = messages.rend(); it != end; ++it) {
+ if((*it)->related_event_type != RelatedEventType::EDIT && (*it)->related_event_type != RelatedEventType::REDACTION) {
+ last_message = (*it).get();
+ break;
+ }
+ }
+ if(last_message && !messages.empty())
+ last_message = messages.back().get();
+ if(last_message)
+ room_body_item->set_description(matrix->message_get_author_displayname(last_message) + ": " + extract_first_line_elipses(last_message->body, 150));
+ }
+
+ room->release_room_lock();
}
void MatrixQuickMedia::update_pending_room_messages(MatrixPageType page_type) {
@@ -349,7 +430,6 @@ namespace QuickMedia {
if(!it.second.sync_is_cache) {
for(auto &message : messages) {
if(message->mentions_me) {
- room->has_unread_mention = true;
// 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 || is_initial_sync || page_type == MatrixPageType::ROOM_LIST)
show_notification("QuickMedia matrix - " + matrix->message_get_author_displayname(message.get()) + " (" + room->get_name() + ")", message->body);
@@ -357,48 +437,7 @@ namespace QuickMedia {
}
}
- std::shared_ptr<UserInfo> me = matrix->get_me(room);
- time_t read_marker_message_timestamp = 0;
- if(me) {
- auto read_marker_message = room->get_message_by_id(room->get_user_read_marker(me));
- if(read_marker_message)
- read_marker_message_timestamp = read_marker_message->timestamp;
- }
-
- // TODO: this wont always work because we dont display all types of messages from server, such as "joined", "left", "kicked", "banned", "changed avatar", "changed display name", etc.
- // TODO: Binary search?
- Message *last_unread_message = nullptr;
- for(auto it = messages.rbegin(), end = messages.rend(); it != end; ++it) {
- if((*it)->related_event_type != RelatedEventType::EDIT && (*it)->related_event_type != RelatedEventType::REDACTION && (*it)->timestamp > read_marker_message_timestamp) {
- last_unread_message = (*it).get();
- break;
- }
- }
-
- BodyItem *room_body_item = static_cast<BodyItem*>(room->userdata);
- assert(room_body_item);
-
- if(last_unread_message) {
- std::string room_desc = "Unread: " + matrix->message_get_author_displayname(last_unread_message) + ": " + extract_first_line_elipses(last_unread_message->body, 150);
- if(room->has_unread_mention)
- room_desc += "\n** You were mentioned **"; // TODO: Better notification?
- room_body_item->set_description(std::move(room_desc));
- room_body_item->set_title_color(sf::Color(255, 100, 100));
- room->last_message_read = false;
-
- rooms_page->move_room_to_top(room);
- room_tags_page->move_room_to_top(room);
- } else if(is_initial_sync) {
- Message *last_message = nullptr;
- for(auto it = messages.rbegin(), end = messages.rend(); it != end; ++it) {
- if((*it)->related_event_type != RelatedEventType::EDIT && (*it)->related_event_type != RelatedEventType::REDACTION) {
- last_message = (*it).get();
- break;
- }
- }
- if(last_message)
- room_body_item->set_description(matrix->message_get_author_displayname(last_message) + ": " + extract_first_line_elipses(last_message->body, 150));
- }
+ update_room_description(room, is_initial_sync);
}
pending_room_messages.clear();
}
@@ -443,6 +482,10 @@ namespace QuickMedia {
body->clamp_selection();
body->append_items(std::move(room_body_items));
}
+ if(sort_on_update) {
+ sort_on_update = false;
+ sort_room_body_items(body->items);
+ }
matrix_delegate->update(MatrixPageType::ROOM_LIST);
}
@@ -460,7 +503,7 @@ namespace QuickMedia {
if(room_body_index != -1) {
std::shared_ptr<BodyItem> body_item = body->items[room_body_index];
int body_swap_index = -1;
- if(room->has_unread_mention)
+ 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());
@@ -493,6 +536,10 @@ namespace QuickMedia {
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);
@@ -607,6 +654,12 @@ namespace QuickMedia {
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) : Page(program), matrix(matrix), body(body) {
}
@@ -775,7 +828,7 @@ namespace QuickMedia {
sync_is_cache = false;
sync_running = true;
- sync_thread = std::thread([this, delegate, matrix_cache_dir]() {
+ sync_thread = std::thread([this, matrix_cache_dir]() {
sync_is_cache = true;
FILE *sync_cache_file = fopen(matrix_cache_dir.data.c_str(), "rb");
if(sync_cache_file) {
@@ -786,13 +839,24 @@ namespace QuickMedia {
rapidjson::ParseResult parse_result = doc.ParseStream<rapidjson::kParseStopWhenDoneFlag>(is);
if(parse_result.IsError())
break;
- if(parse_sync_response(doc, delegate) != PluginResult::OK)
+ if(parse_sync_response(doc) != PluginResult::OK)
fprintf(stderr, "Failed to parse cached sync response\n");
}
fclose(sync_cache_file);
}
sync_is_cache = false;
+ // Filter with account data. TODO: Test if this is needed for encrypted chats
+ // {"presence":{"limit":0,"types":[""]},"account_data":{"not_types":["im.vector.setting.breadcrumbs","m.push_rules","im.vector.setting.allowed_widgets","io.element.recent_emoji"]},"room":{"state":{"limit":1,"not_types":["m.room.related_groups","m.room.power_levels","m.room.join_rules","m.room.history_visibility"],"lazy_load_members":true},"timeline":{"limit":3,"lazy_load_members":true},"ephemeral":{"limit":0,"types":[""],"lazy_load_members":true},"account_data":{"limit":1,"types":["m.fully_read"],"lazy_load_members":true}}}
+ // Filter without account data
+ const char *filter = "{\"presence\":{\"limit\":0,\"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\"],\"lazy_load_members\":true},\"timeline\":{\"limit\":3,\"lazy_load_members\":true},\"ephemeral\":{\"limit\":0,\"types\":[\"\"],\"lazy_load_members\":true},\"account_data\":{\"limit\":1,\"types\":[\"m.fully_read\",\"m.tag\"],\"lazy_load_members\":true}}}";
+ const std::string filter_encoded = url_param_encode(filter);
+
+ std::vector<CommandArg> additional_args = {
+ { "-H", "Authorization: Bearer " + access_token },
+ { "-m", "35" }
+ };
+
const rapidjson::Value *next_batch_json;
PluginResult result;
bool initial_sync = true;
@@ -802,40 +866,43 @@ namespace QuickMedia {
{ "-m", "35" }
};
- char url[512];
+ char url[1024];
if(next_batch.empty())
- snprintf(url, sizeof(url), "%s/_matrix/client/r0/sync?timeout=0", homeserver.c_str());
+ snprintf(url, sizeof(url), "%s/_matrix/client/r0/sync?filter=%s&timeout=0", homeserver.c_str(), filter_encoded.c_str());
else
- snprintf(url, sizeof(url), "%s/_matrix/client/r0/sync?timeout=30000&since=%s", homeserver.c_str(), next_batch.c_str());
+ snprintf(url, sizeof(url), "%s/_matrix/client/r0/sync?filter=%s&timeout=30000&since=%s", homeserver.c_str(), filter_encoded.c_str(), next_batch.c_str());
rapidjson::Document json_root;
std::string err_msg;
- DownloadResult download_result = download_json(json_root, url, std::move(additional_args), true, &err_msg);
+ DownloadResult download_result = download_json(json_root, url, additional_args, true, &err_msg);
if(download_result != DownloadResult::OK) {
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(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;
}
}
+ fprintf(stderr, "/sync failed\n");
+ goto sync_end;
}
- goto sync_end;
}
if(next_batch.empty())
clear_sync_cache_for_new_sync();
- result = parse_sync_response(json_root, delegate);
+ result = parse_sync_response(json_root);
if(result != PluginResult::OK) {
fprintf(stderr, "Failed to parse sync response\n");
goto sync_end;
@@ -850,22 +917,44 @@ namespace QuickMedia {
fprintf(stderr, "Matrix: missing next batch\n");
}
+ if(initial_sync) {
+ notification_thread = std::thread([this]() {
+ std::vector<CommandArg> additional_args = {
+ { "-H", "Authorization: Bearer " + access_token }
+ };
+
+ // TODO: Instead of guessing notification limit with 100, accumulate rooms unread_notifications count and use that as the limit
+ // (and take into account that notification response may have notifications after call to sync above).
+ char url[512];
+ snprintf(url, sizeof(url), "%s/_matrix/client/r0/notifications?limit=100", homeserver.c_str());
+
+ rapidjson::Document json_root;
+ DownloadResult download_result = download_json(json_root, url, std::move(additional_args), true);
+ if(download_result != DownloadResult::OK || !json_root.IsObject()) {
+ fprintf(stderr, "Fetching notifications failed!\n");
+ return;
+ }
+
+ const rapidjson::Value &notification_json = GetMember(json_root, "notifications");
+ parse_notifications(notification_json);
+ });
+ }
+
sync_end:
if(sync_running)
- std::this_thread::sleep_for(std::chrono::seconds(1));
-
- if(!json_root.IsObject())
- continue;
+ std::this_thread::sleep_for(std::chrono::milliseconds(500));
// 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);
+ 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);
+ json_root.Accept(writer);
+ }
fclose(sync_cache_file);
}
}
@@ -874,9 +963,17 @@ namespace QuickMedia {
void Matrix::stop_sync() {
sync_running = false;
- program_kill_in_thread(sync_thread.get_id());
- if(sync_thread.joinable())
+
+ if(sync_thread.joinable()) {
+ program_kill_in_thread(sync_thread.get_id());
sync_thread.join();
+ }
+
+ if(notification_thread.joinable()) {
+ program_kill_in_thread(notification_thread.get_id());
+ notification_thread.join();
+ }
+
delegate = nullptr;
sync_failed = false;
sync_fail_reason.clear();
@@ -903,6 +1000,7 @@ namespace QuickMedia {
room->messages_read_index = room_messages.size();
} else {
fprintf(stderr, "Unexpected behavior!!!! get_room_sync_data said read index is %zu but we only have %zu messages\n", room->messages_read_index, room_messages.size());
+ room->messages_read_index = room_messages.size();
}
if(room->pinned_events_updated) {
sync_data.pinned_events = room->get_pinned_events_unsafe();
@@ -938,22 +1036,75 @@ namespace QuickMedia {
size_t num_new_messages = num_messages_after - num_messages_before;
messages.insert(messages.end(), room->get_messages_thread_unsafe().begin(), room->get_messages_thread_unsafe().begin() + num_new_messages);
room->messages_read_index += num_new_messages;
+ assert(room->messages_read_index <= room->get_messages_thread_unsafe().size());
room->release_room_lock();
return PluginResult::OK;
}
- PluginResult Matrix::parse_sync_response(const rapidjson::Document &root, MatrixDelegate *delegate) {
+ PluginResult Matrix::parse_sync_response(const rapidjson::Document &root) {
if(!root.IsObject())
return PluginResult::ERR;
- const rapidjson::Value &account_data_json = GetMember(root, "account_data");
- std::optional<std::set<std::string>> dm_rooms;
- parse_sync_account_data(account_data_json, dm_rooms);
+ //const rapidjson::Value &account_data_json = GetMember(root, "account_data");
+ //std::optional<std::set<std::string>> dm_rooms;
+ //parse_sync_account_data(account_data_json, dm_rooms);
// TODO: Include "Direct messages" as a tag using |dm_rooms| above
const rapidjson::Value &rooms_json = GetMember(root, "rooms");
- parse_sync_room_data(rooms_json, delegate);
+ parse_sync_room_data(rooms_json);
+
+ return PluginResult::OK;
+ }
+
+ PluginResult Matrix::parse_notifications(const rapidjson::Value &notifications_json) {
+ if(!notifications_json.IsArray())
+ return PluginResult::ERR;
+ for(const rapidjson::Value &notification_json : notifications_json.GetArray()) {
+ if(!notification_json.IsObject())
+ continue;
+
+ const rapidjson::Value &read_json = GetMember(notification_json, "read");
+ if(!read_json.IsBool() || read_json.GetBool())
+ continue;
+
+ const rapidjson::Value &room_id_json = GetMember(notification_json, "room_id");
+ if(!room_id_json.IsString())
+ continue;
+
+ const rapidjson::Value &event_json = GetMember(notification_json, "event");
+ if(!event_json.IsObject())
+ continue;
+
+ const rapidjson::Value &event_id_json = GetMember(event_json, "event_id");
+ if(!event_id_json.IsString())
+ continue;
+
+ const rapidjson::Value &sender_json = GetMember(event_json, "sender");
+ if(!sender_json.IsString())
+ continue;
+
+ const rapidjson::Value &content_json = GetMember(event_json, "content");
+ if(!content_json.IsObject())
+ continue;
+
+ const rapidjson::Value &body_json = GetMember(content_json, "body");
+ if(!body_json.IsString())
+ continue;
+
+ std::string room_id(room_id_json.GetString(), room_id_json.GetStringLength());
+ RoomData *room = get_room_by_id(room_id);
+ if(!room) {
+ fprintf(stderr, "Warning: got notification in unknown room %s\n", room_id.c_str());
+ continue;
+ }
+ room->unread_notification_count++;
+
+ 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));
+ }
return PluginResult::OK;
}
@@ -999,7 +1150,7 @@ namespace QuickMedia {
return PluginResult::OK;
}
- PluginResult Matrix::parse_sync_room_data(const rapidjson::Value &rooms_json, MatrixDelegate *delegate) {
+ PluginResult Matrix::parse_sync_room_data(const rapidjson::Value &rooms_json) {
if(!rooms_json.IsObject())
return PluginResult::OK;
@@ -1033,7 +1184,7 @@ namespace QuickMedia {
events_add_pinned_events(events_json, room);
}
- const rapidjson::Value &ephemeral_json = GetMember(it.value, "ephemeral");
+ const rapidjson::Value &account_data_json = GetMember(it.value, "account_data");
const rapidjson::Value &timeline_json = GetMember(it.value, "timeline");
if(timeline_json.IsObject()) {
@@ -1047,27 +1198,38 @@ namespace QuickMedia {
// TODO: Use /_matrix/client/r0/notifications ? or remove this and always look for displayname/user_id in messages
bool has_unread_notifications = false;
const rapidjson::Value &unread_notification_json = GetMember(it.value, "unread_notifications");
- if(unread_notification_json.IsObject()) {
+ if(unread_notification_json.IsObject() && is_initial_sync_finished()) {
const rapidjson::Value &highlight_count_json = GetMember(unread_notification_json, "highlight_count");
- if(highlight_count_json.IsNumber() && highlight_count_json.GetInt64() > 0)
+ if(highlight_count_json.IsNumber() && highlight_count_json.GetInt64() > 0) {
+ room->unread_notification_count = highlight_count_json.GetInt64();
has_unread_notifications = true;
+ }
}
const rapidjson::Value &events_json = GetMember(timeline_json, "events");
events_add_user_info(events_json, room);
events_set_room_name(events_json, room);
- // We want to do this before adding messages to know if a message that mentions us is a new mention
- if(ephemeral_json.IsObject()) {
- const rapidjson::Value &events_json = GetMember(ephemeral_json, "events");
- events_add_user_read_markers(events_json, room);
+
+ 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_add_messages(events_json, room, MessageDirection::AFTER, delegate, has_unread_notifications);
+
+ if(is_new_room)
+ delegate->join_room(room);
+
+ events_add_messages(events_json, room, MessageDirection::AFTER, has_unread_notifications);
events_add_pinned_events(events_json, room);
} else {
- if(ephemeral_json.IsObject()) {
- const rapidjson::Value &events_json = GetMember(ephemeral_json, "events");
- events_add_user_read_markers(events_json, room);
+ 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);
}
+
+ if(is_new_room)
+ delegate->join_room(room);
}
if(remove_invite(room_id_str)) {
@@ -1075,13 +1237,9 @@ namespace QuickMedia {
delegate->remove_invite(room_id_str);
}
- if(is_new_room)
- delegate->join_room(room);
-
- const rapidjson::Value &account_data_json = GetMember(it.value, "account_data");
if(account_data_json.IsObject()) {
const rapidjson::Value &events_json = GetMember(account_data_json, "events");
- events_add_room_to_tags(events_json, room, delegate);
+ events_add_room_to_tags(events_json, room);
}
if(is_new_room) {
@@ -1097,10 +1255,10 @@ namespace QuickMedia {
}
const rapidjson::Value &leave_json = GetMember(rooms_json, "leave");
- remove_rooms(leave_json, delegate);
+ remove_rooms(leave_json);
const rapidjson::Value &invite_json = GetMember(rooms_json, "invite");
- add_invites(invite_json, delegate);
+ add_invites(invite_json);
return PluginResult::OK;
}
@@ -1158,7 +1316,7 @@ namespace QuickMedia {
if(strncmp(user_info->avatar_url.c_str(), "mxc://", 6) == 0)
user_info->avatar_url.erase(user_info->avatar_url.begin(), user_info->avatar_url.begin() + 6);
if(!user_info->avatar_url.empty())
- user_info->avatar_url = homeserver + "/_matrix/media/r0/thumbnail/" + user_info->avatar_url + "?width=32&height=32&method=crop";
+ user_info->avatar_url = homeserver + "/_matrix/media/r0/thumbnail/" + user_info->avatar_url + "?width=32&height=32&method=crop"; // TODO: Remove the constant strings around to reduce memory usage (6.3mb)
user_info->display_name = display_name_json.IsString() ? display_name_json.GetString() : sender_json_str;
user_info->display_name_color = user_id_to_color(sender_json_str);
@@ -1217,6 +1375,31 @@ namespace QuickMedia {
}
}
+ 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;
+
+ for(const rapidjson::Value &event_json : events_json.GetArray()) {
+ if(!event_json.IsObject())
+ continue;
+
+ const rapidjson::Value &type_json = GetMember(event_json, "type");
+ if(!type_json.IsString() || strcmp(type_json.GetString(), "m.fully_read") != 0)
+ continue;
+
+ const rapidjson::Value &content_json = GetMember(event_json, "content");
+ if(!content_json.IsObject())
+ continue;
+
+ const rapidjson::Value &event_id_json = GetMember(content_json, "event_id");
+ if(!event_id_json.IsString())
+ continue;
+
+ room_data->set_user_read_marker(me, std::string(event_id_json.GetString(), event_id_json.GetStringLength()));
+ }
+ }
+
static std::string message_content_extract_thumbnail_url(const rapidjson::Value &content_json, const std::string &homeserver) {
const rapidjson::Value &info_json = GetMember(content_json, "info");
if(info_json.IsObject()) {
@@ -1323,7 +1506,7 @@ namespace QuickMedia {
return false;
}
- void Matrix::events_add_messages(const rapidjson::Value &events_json, RoomData *room_data, MessageDirection message_dir, MatrixDelegate *delegate, bool has_unread_notifications) {
+ void Matrix::events_add_messages(const rapidjson::Value &events_json, RoomData *room_data, MessageDirection message_dir, bool has_unread_notifications) {
if(!events_json.IsArray())
return;
@@ -1346,18 +1529,20 @@ namespace QuickMedia {
room_data->append_messages(new_messages);
}
- time_t read_marker_message_timestamp = 0;
- if(me) {
- auto read_marker_message = room_data->get_message_by_id(room_data->get_user_read_marker(me));
- if(read_marker_message)
- read_marker_message_timestamp = read_marker_message->timestamp;
- }
+ if(is_initial_sync_finished()) {
+ time_t read_marker_message_timestamp = 0;
+ if(me) {
+ auto read_marker_message = room_data->get_message_by_id(room_data->get_user_read_marker(me));
+ if(read_marker_message)
+ read_marker_message_timestamp = read_marker_message->timestamp;
+ }
- for(auto &message : new_messages) {
- // TODO: Is @room ok? shouldn't we also check if the user has permission to do @room? (only when notifications are limited to @mentions)
- // TODO: Is comparing against read marker timestamp ok enough?
- if(has_unread_notifications && me && message->timestamp > read_marker_message_timestamp)
- message->mentions_me = message_contains_user_mention(message->body, me->display_name) || message_contains_user_mention(message->body, me->user_id) || message_contains_user_mention(message->body, "@room");
+ for(auto &message : new_messages) {
+ // TODO: Is @room ok? shouldn't we also check if the user has permission to do @room? (only when notifications are limited to @mentions)
+ // TODO: Is comparing against read marker timestamp ok enough?
+ if(has_unread_notifications && me && message->timestamp > read_marker_message_timestamp)
+ message->mentions_me = message_contains_user_mention(message->body, me->display_name) || message_contains_user_mention(message->body, me->user_id) || message_contains_user_mention(message->body, "@room");
+ }
}
if(delegate)
@@ -1426,7 +1611,7 @@ namespace QuickMedia {
content_json = &new_content_json;
const rapidjson::Value &content_type = GetMember(*content_json, "msgtype");
- if(!content_type.IsString() || strcmp(type_json.GetString(), "m.room.redaction") == 0) {
+ if(strcmp(type_json.GetString(), "m.room.redaction") == 0) {
auto message = std::make_shared<Message>();
message->type = MessageType::REDACTION;
message->user = user;
@@ -1460,7 +1645,7 @@ namespace QuickMedia {
// TODO: Also show joins, leave, invites, bans, kicks, mutes, etc
- if(strcmp(content_type.GetString(), "m.text") == 0) {
+ if(!content_type.IsString() || strcmp(content_type.GetString(), "m.text") == 0) {
message->type = MessageType::TEXT;
} else if(strcmp(content_type.GetString(), "m.image") == 0) {
const rapidjson::Value &url_json = GetMember(*content_json, "url");
@@ -1665,7 +1850,7 @@ namespace QuickMedia {
room_data->set_pinned_events(std::move(pinned_events));
}
- void Matrix::events_add_room_to_tags(const rapidjson::Value &events_json, RoomData *room_data, MatrixDelegate *delegate) {
+ void Matrix::events_add_room_to_tags(const rapidjson::Value &events_json, RoomData *room_data) {
if(!events_json.IsArray())
return;
@@ -1730,7 +1915,7 @@ namespace QuickMedia {
}
}
- void Matrix::add_invites(const rapidjson::Value &invite_json, MatrixDelegate *delegate) {
+ void Matrix::add_invites(const rapidjson::Value &invite_json) {
if(!invite_json.IsObject())
return;
@@ -1801,7 +1986,7 @@ namespace QuickMedia {
}
}
- void Matrix::remove_rooms(const rapidjson::Value &leave_json, MatrixDelegate *delegate) {
+ void Matrix::remove_rooms(const rapidjson::Value &leave_json) {
if(!leave_json.IsObject())
return;
@@ -1917,12 +2102,12 @@ namespace QuickMedia {
if(!json_root.IsObject())
return PluginResult::ERR;
- //const rapidjson::Value &state_json = GetMember(json_root, "state");
- //events_add_user_info(state_json, room_data);
- //events_set_room_name(state_json, room_data);
+ const rapidjson::Value &state_json = GetMember(json_root, "state");
+ events_add_user_info(state_json, room_data);
+ events_set_room_name(state_json, room_data);
const rapidjson::Value &chunk_json = GetMember(json_root, "chunk");
- events_add_messages(chunk_json, room_data, MessageDirection::BEFORE, nullptr, false);
+ events_add_messages(chunk_json, room_data, MessageDirection::BEFORE, false);
const rapidjson::Value &end_json = GetMember(json_root, "end");
if(!end_json.IsString()) {
@@ -2154,7 +2339,6 @@ namespace QuickMedia {
PluginResult Matrix::post_reply(RoomData *room, const std::string &body, void *relates_to) {
// TODO: Store shared_ptr<Message> instead of raw pointer...
Message *relates_to_message_raw = (Message*)relates_to;
- std::shared_ptr<Message> relates_to_message_shared = room->get_message_by_id(relates_to_message_raw->event_id);
char random_characters[18];
if(!generate_random_characters(random_characters, sizeof(random_characters)))
@@ -2163,7 +2347,7 @@ namespace QuickMedia {
std::string random_readable_chars = random_characters_to_readable_string(random_characters, sizeof(random_characters));
rapidjson::Document in_reply_to_json(rapidjson::kObjectType);
- in_reply_to_json.AddMember("event_id", rapidjson::StringRef(relates_to_message_shared->event_id.c_str()), in_reply_to_json.GetAllocator());
+ in_reply_to_json.AddMember("event_id", rapidjson::StringRef(relates_to_message_raw->event_id.c_str()), in_reply_to_json.GetAllocator());
rapidjson::Document relates_to_json(rapidjson::kObjectType);
relates_to_json.AddMember("m.in_reply_to", std::move(in_reply_to_json), relates_to_json.GetAllocator());
@@ -2209,7 +2393,6 @@ namespace QuickMedia {
PluginResult Matrix::post_edit(RoomData *room, const std::string &body, void *relates_to) {
Message *relates_to_message_raw = (Message*)relates_to;
- std::shared_ptr<Message> relates_to_message_shared = room->get_message_by_id(relates_to_message_raw->event_id);
char random_characters[18];
if(!generate_random_characters(random_characters, sizeof(random_characters)))
@@ -2246,7 +2429,7 @@ namespace QuickMedia {
}
rapidjson::Document relates_to_json(rapidjson::kObjectType);
- relates_to_json.AddMember("event_id", rapidjson::StringRef(relates_to_message_shared->event_id.c_str()), relates_to_json.GetAllocator());
+ relates_to_json.AddMember("event_id", rapidjson::StringRef(relates_to_message_raw->event_id.c_str()), relates_to_json.GetAllocator());
relates_to_json.AddMember("rel_type", "m.replace", relates_to_json.GetAllocator());
std::string body_edit_str = " * " + body;
@@ -2300,29 +2483,47 @@ 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/event/%s", homeserver.c_str(), room->id.c_str(), event_id.c_str());
+ 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());
std::string err_msg;
rapidjson::Document json_root;
DownloadResult download_result = download_json(json_root, url, std::move(additional_args), true, &err_msg);
if(download_result != DownloadResult::OK) return nullptr;
- if(json_root.IsObject()) {
- const rapidjson::Value &error_json = GetMember(json_root, "error");
- if(error_json.IsString()) {
- fprintf(stderr, "Matrix::get_message_by_id, error: %s\n", error_json.GetString());
- room->fetched_messages_by_event_id.insert(std::make_pair(event_id, nullptr));
- return nullptr;
- }
+ if(!json_root.IsObject()) {
+ fprintf(stderr, "Failed to get message by id %s, error: %s\n", event_id.c_str(), err_msg.c_str());
+ room->fetched_messages_by_event_id.insert(std::make_pair(event_id, nullptr));
+ return nullptr;
}
- std::shared_ptr<Message> new_message = parse_message_event(json_root, room);
+ const rapidjson::Value &error_json = GetMember(json_root, "error");
+ if(error_json.IsString()) {
+ fprintf(stderr, "Matrix::get_message_by_id, error: %s\n", error_json.GetString());
+ room->fetched_messages_by_event_id.insert(std::make_pair(event_id, nullptr));
+ return nullptr;
+ }
+
+ const rapidjson::Value &state_json = GetMember(json_root, "state");
+ events_add_user_info(state_json, room);
+ events_set_room_name(state_json, room);
+
+ const rapidjson::Value &event_json = GetMember(json_root, "event");
+ std::shared_ptr<Message> new_message = parse_message_event(event_json, room);
room->fetched_messages_by_event_id.insert(std::make_pair(event_id, new_message));
return new_message;
}
@@ -2443,6 +2644,7 @@ namespace QuickMedia {
}
PluginResult Matrix::login(const std::string &username, const std::string &password, const std::string &homeserver, std::string &err_msg) {
+ assert(!sync_running);
rapidjson::Document identifier_json(rapidjson::kObjectType);
identifier_json.AddMember("type", "m.id.user", identifier_json.GetAllocator()); // TODO: What if the server doesn't support this login type? redirect to sso web page etc
identifier_json.AddMember("user", rapidjson::StringRef(username.c_str()), identifier_json.GetAllocator());