aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/QuickMedia.cpp167
-rw-r--r--src/plugins/Matrix.cpp261
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, &current_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, &current_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, &current_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, &current_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;