aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2020-09-25 04:15:17 +0200
committerdec05eba <dec05eba@protonmail.com>2020-09-25 04:15:17 +0200
commiteac2ace1c14c1ae0564d757b26a359c6bd4b754a (patch)
tree855b98a19b6a302663a2d26e3ed6ca69110d1ce0
parent6cb237cfba67e1a15d475dccfb706bcc179afe71 (diff)
Matrix: fetch previous messages when reaching the top
-rw-r--r--include/Body.hpp1
-rw-r--r--include/SearchBar.hpp3
-rw-r--r--plugins/Matrix.hpp9
-rw-r--r--src/Body.cpp8
-rw-r--r--src/QuickMedia.cpp75
-rw-r--r--src/SearchBar.cpp2
-rw-r--r--src/plugins/Matrix.cpp117
7 files changed, 151 insertions, 64 deletions
diff --git a/include/Body.hpp b/include/Body.hpp
index f246b5a..79bece6 100644
--- a/include/Body.hpp
+++ b/include/Body.hpp
@@ -83,6 +83,7 @@ namespace QuickMedia {
void select_first_item();
void reset_selected();
void clear_items();
+ void prepend_items(BodyItems new_items);
void append_items(BodyItems new_items);
void clear_thumbnails();
diff --git a/include/SearchBar.hpp b/include/SearchBar.hpp
index b4abd77..a90c69d 100644
--- a/include/SearchBar.hpp
+++ b/include/SearchBar.hpp
@@ -43,6 +43,9 @@ namespace QuickMedia {
int text_autosearch_delay;
int autocomplete_search_delay;
bool caret_visible;
+
+ float padding_vertical = 20.0f;
+ float background_margin_vertical = 4.0f;
private:
void clear_autocomplete_if_text_not_substring();
void clear_autocomplete_if_last_char_not_substr();
diff --git a/plugins/Matrix.hpp b/plugins/Matrix.hpp
index 607ef81..396fc17 100644
--- a/plugins/Matrix.hpp
+++ b/plugins/Matrix.hpp
@@ -43,6 +43,7 @@ namespace QuickMedia {
std::string avatar_url;
std::string prev_batch;
bool initial_fetch_finished = false;
+ size_t last_read_index = 0;
};
enum class MessageDirection {
@@ -62,9 +63,9 @@ namespace QuickMedia {
PluginResult get_cached_sync(BodyItems &result_items);
PluginResult sync();
PluginResult get_joined_rooms(BodyItems &result_items);
- // Note: the number of items returned in |result_items| may not be the number of new messages because many messages can be combined
- // into one if one user sends multiple messages. The number of messages is returned in |num_new_messages|.
- PluginResult get_room_messages(const std::string &room_id, size_t start_index, BodyItems &result_items, size_t &num_new_messages);
+ PluginResult get_all_synced_room_messages(const std::string &room_id, BodyItems &result_items);
+ PluginResult get_new_room_messages(const std::string &room_id, BodyItems &result_items);
+ PluginResult get_previous_room_messages(const std::string &room_id, BodyItems &result_items);
SearchResult search(const std::string &text, BodyItems &result_items) override;
// |url| should only be set when uploading media.
@@ -76,7 +77,7 @@ namespace QuickMedia {
PluginResult load_and_verify_cached_session();
private:
PluginResult sync_response_to_body_items(const Json::Value &root);
- PluginResult load_initial_room_data(const std::string &room_id, RoomData *room_data);
+ PluginResult get_previous_room_messages(const std::string &room_id, RoomData *room_data);
void events_add_user_info(const Json::Value &events_json, RoomData *room_data);
void events_add_messages(const Json::Value &events_json, RoomData *room_data, MessageDirection message_dir);
void events_set_room_name(const Json::Value &events_json, RoomData *room_data);
diff --git a/src/Body.cpp b/src/Body.cpp
index 52999e5..f97d611 100644
--- a/src/Body.cpp
+++ b/src/Body.cpp
@@ -157,6 +157,14 @@ namespace QuickMedia {
selected_item = 0;
}
+ // TODO: Optimize with memcpy and changing capacity before loop
+ void Body::prepend_items(BodyItems new_items) {
+ for(auto &body_item : new_items) {
+ items.insert(items.begin(), std::move(body_item));
+ }
+ }
+
+ // TODO: Optimize with memcpy and changing capacity before loop
void Body::append_items(BodyItems new_items) {
for(auto &body_item : new_items) {
items.push_back(std::move(body_item));
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index e367ae0..c701c14 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -3188,12 +3188,13 @@ namespace QuickMedia {
std::vector<ChatTab> tabs;
int selected_tab = 0;
- size_t room_message_index = 0;
ChatTab messages_tab;
messages_tab.type = ChatTabType::MESSAGES;
messages_tab.body = std::make_unique<Body>(this, font.get(), bold_font.get(), cjk_font.get());
messages_tab.body->draw_thumbnails = true;
+ messages_tab.body->thumbnail_resize_target_size.x = 600;
+ messages_tab.body->thumbnail_resize_target_size.y = 337;
//messages_tab.body->line_seperator_color = sf::Color::Transparent;
messages_tab.text = sf::Text("Messages", *font, tab_text_size);
tabs.push_back(std::move(messages_tab));
@@ -3216,6 +3217,8 @@ namespace QuickMedia {
fprintf(stderr, "Loaded matrix sync from cache, num items: %zu\n", tabs[MESSAGES_TAB_INDEX].body->items.size());
}
*/
+ // This is needed to get initial data, with joined rooms etc. TODO: Remove this once its cached
+ // and allow asynchronous update of rooms
if(matrix->sync() != PluginResult::OK) {
show_notification("QuickMedia", "Intial matrix sync failed", Urgency::CRITICAL);
current_page = Page::EXIT;
@@ -3234,9 +3237,11 @@ namespace QuickMedia {
if(!tabs[ROOMS_TAB_INDEX].body->items.empty())
current_room_id = tabs[ROOMS_TAB_INDEX].body->items[0]->url;
- // TODO: Allow empty initial room (if the user hasn't joined any room yet)
+ // TODO: Allow empty initial room (if the user hasn't joined any room yet).
assert(!current_room_id.empty());
+ // get_all_room_messages is not needed here because its done in the loop, where the initial timeout is 0ms
+
{
std::string plugin_logo_path = resources_root + "images/matrix_logo.png";
if(!plugin_logo.loadFromFile(plugin_logo_path)) {
@@ -3249,13 +3254,16 @@ namespace QuickMedia {
SearchBar chat_input(*font, &plugin_logo, "Send a message...");
chat_input.set_background_color(sf::Color::Transparent);
+ chat_input.padding_vertical = 10.0f;
+
+ // TODO: Scroll to bottom when receiving new messages, but only if we are already at the bottom?
// TODO: Filer for rooms and settings
chat_input.onTextUpdateCallback = nullptr;
// TODO: Show post message immediately, instead of waiting for sync. Otherwise it can take a while until we receive the message,
// which happens when uploading an image.
- chat_input.onTextSubmitCallback = [this, matrix, &tabs, &selected_tab, &room_message_index, &current_room_id](const std::string &text) -> bool {
+ chat_input.onTextSubmitCallback = [this, matrix, &tabs, &selected_tab, &current_room_id](const std::string &text) -> bool {
if(tabs[selected_tab].type == ChatTabType::MESSAGES) {
if(text.empty())
return false;
@@ -3299,17 +3307,12 @@ namespace QuickMedia {
if(selected_item) {
current_room_id = selected_item->url;
selected_tab = MESSAGES_TAB_INDEX;
- room_message_index = 0;
tabs[MESSAGES_TAB_INDEX].body->clear_items();
- size_t num_new_messages = 0;
BodyItems new_items;
// TODO: Make asynchronous
- if(matrix->get_room_messages(current_room_id, 0, new_items, num_new_messages) == PluginResult::OK) {
- room_message_index += num_new_messages;
+ if(matrix->get_all_synced_room_messages(current_room_id, new_items) == PluginResult::OK) {
tabs[MESSAGES_TAB_INDEX].body->append_items(std::move(new_items));
- if(!tabs[MESSAGES_TAB_INDEX].body->items.empty() && num_new_messages > 0)
- tabs[MESSAGES_TAB_INDEX].body->set_selected_item(tabs[MESSAGES_TAB_INDEX].body->items.size() - 1);
} else {
std::string err_msg = "Failed to get messages in room: " + current_room_id;
show_notification("QuickMedia", err_msg, Urgency::CRITICAL);
@@ -3320,17 +3323,16 @@ namespace QuickMedia {
return false;
};
- struct SyncFutureResult {
- BodyItems body_items;
- size_t num_new_messages;
- };
-
- std::future<SyncFutureResult> sync_future;
+ std::future<BodyItems> sync_future;
bool sync_running = false;
std::string sync_future_room_id;
sf::Clock sync_timer;
sf::Int32 sync_min_time_ms = 0; // Sync immediately the first time
+ std::future<BodyItems> previous_messages_future;
+ bool fetching_previous_messages_running = false;
+ std::string previous_messages_future_room_id;
+
const float tab_spacer_height = 0.0f;
sf::Vector2f body_pos;
sf::Vector2f body_size;
@@ -3350,7 +3352,19 @@ namespace QuickMedia {
redraw = true;
} else if(event.type == sf::Event::KeyPressed) {
if(event.key.code == sf::Keyboard::Up) {
- tabs[selected_tab].body->select_previous_item();
+ bool item_changed = tabs[selected_tab].body->select_previous_item();
+ // Top hit
+ if(!item_changed && !fetching_previous_messages_running) {
+ fetching_previous_messages_running = true;
+ previous_messages_future_room_id = current_room_id;
+ previous_messages_future = std::async(std::launch::async, [this, &previous_messages_future_room_id]() {
+ Matrix *matrix = static_cast<Matrix*>(current_plugin);
+ BodyItems result_items;
+ if(matrix->get_previous_room_messages(previous_messages_future_room_id, result_items) != PluginResult::OK)
+ fprintf(stderr, "Failed to get previous matrix messages in room: %s\n", previous_messages_future_room_id.c_str());
+ return result_items;
+ });
+ }
} else if(event.key.code == sf::Keyboard::Down) {
tabs[selected_tab].body->select_next_item();
} else if(event.key.code == sf::Keyboard::Escape) {
@@ -3420,36 +3434,45 @@ namespace QuickMedia {
sync_running = true;
sync_timer.restart();
sync_future_room_id = current_room_id;
- sync_future = std::async(std::launch::async, [this, &sync_future_room_id, room_message_index]() {
+ sync_future = std::async(std::launch::async, [this, &sync_future_room_id]() {
Matrix *matrix = static_cast<Matrix*>(current_plugin);
- SyncFutureResult result;
- result.num_new_messages = 0;
+ BodyItems result_items;
if(matrix->sync() == PluginResult::OK) {
fprintf(stderr, "Synced matrix\n");
- if(matrix->get_room_messages(sync_future_room_id, room_message_index, result.body_items, result.num_new_messages) != PluginResult::OK) {
+ if(matrix->get_new_room_messages(sync_future_room_id, result_items) != PluginResult::OK) {
fprintf(stderr, "Failed to get new matrix messages in room: %s\n", sync_future_room_id.c_str());
}
} else {
fprintf(stderr, "Failed to sync matrix\n");
}
- return result;
+ return result_items;
});
}
if(sync_future.valid() && sync_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
- SyncFutureResult sync_future_result = sync_future.get();
+ BodyItems new_body_items = sync_future.get();
// Ignore finished sync if it happened in another room. When we navigate back to the room we will get the messages again
if(sync_future_room_id == current_room_id) {
- room_message_index += sync_future_result.num_new_messages;
- tabs[MESSAGES_TAB_INDEX].body->append_items(std::move(sync_future_result.body_items));
- if(!tabs[MESSAGES_TAB_INDEX].body->items.empty() && sync_future_result.num_new_messages > 0)
- tabs[MESSAGES_TAB_INDEX].body->set_selected_item(tabs[MESSAGES_TAB_INDEX].body->items.size() - 1);
+ tabs[MESSAGES_TAB_INDEX].body->append_items(std::move(new_body_items));
}
sync_running = false;
}
+ if(previous_messages_future.valid() && previous_messages_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
+ BodyItems new_body_items = previous_messages_future.get();
+ fprintf(stderr, "Finished fetching older messages, num new messages: %zu\n", new_body_items.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
+ if(previous_messages_future_room_id == current_room_id) {
+ size_t num_new_messages = new_body_items.size();
+ int selected_item_index = tabs[MESSAGES_TAB_INDEX].body->get_selected_item();
+ tabs[MESSAGES_TAB_INDEX].body->prepend_items(std::move(new_body_items));
+ tabs[MESSAGES_TAB_INDEX].body->set_selected_item(selected_item_index + num_new_messages);
+ }
+ fetching_previous_messages_running = false;
+ }
+
chat_input.update();
window.clear(back_color);
diff --git a/src/SearchBar.cpp b/src/SearchBar.cpp
index ad6f709..e646546 100644
--- a/src/SearchBar.cpp
+++ b/src/SearchBar.cpp
@@ -8,9 +8,7 @@
const sf::Color text_placeholder_color(255, 255, 255, 100);
const sf::Color front_color(55, 60, 68);
const float background_margin_horizontal = 15.0f;
-const float background_margin_vertical = 4.0f;
const float PADDING_HORIZONTAL = 25.0f;
-const float padding_vertical = 20.0f;
namespace QuickMedia {
SearchBar::SearchBar(sf::Font &font, sf::Texture *plugin_logo, const std::string &placeholder, bool input_masked) :
diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp
index 2d4fd4a..529d42a 100644
--- a/src/plugins/Matrix.cpp
+++ b/src/plugins/Matrix.cpp
@@ -159,9 +159,37 @@ namespace QuickMedia {
return PluginResult::OK;
}
- PluginResult Matrix::get_room_messages(const std::string &room_id, size_t start_index, BodyItems &result_items, size_t &num_new_messages) {
- num_new_messages = 0;
+ static void room_messages_to_body_items(RoomData *room_data, Message *messages, size_t num_messages, BodyItems &result_items) {
+ // TODO: This prev_user_id should check previous message, otherwise if after a sync there is only 1 new message then it wont
+ // merge with the previous message. But for that to work then also have to send existing body items to this function,
+ // to get the body item to merge with
+ size_t prev_user_id = -1;
+ for(size_t i = 0; i < num_messages; ++i) {
+ const UserInfo &user_info = room_data->user_info[messages[i].user_id];
+ if(messages[i].user_id == prev_user_id && messages[i].url.empty()) {
+ assert(!result_items.empty());
+ result_items.back()->append_description("\n");
+ result_items.back()->append_description(messages[i].body);
+ } else {
+ auto body_item = std::make_unique<BodyItem>("");
+ body_item->set_author(user_info.display_name);
+ body_item->set_description(messages[i].body);
+ if(!messages[i].thumbnail_url.empty())
+ body_item->thumbnail_url = messages[i].thumbnail_url;
+ else if(!messages[i].url.empty())
+ body_item->thumbnail_url = messages[i].url;
+ else
+ body_item->thumbnail_url = user_info.avatar_url;
+ // TODO: Show image thumbnail inline instead of url to image
+ body_item->url = messages[i].url;
+ result_items.push_back(std::move(body_item));
+ prev_user_id = messages[i].user_id;
+ }
+ }
+ }
+ // TODO: Merge common code with |get_new_room_messages|
+ PluginResult Matrix::get_all_synced_room_messages(const std::string &room_id, BodyItems &result_items) {
auto room_it = room_data_by_id.find(room_id);
if(room_it == room_data_by_id.end()) {
fprintf(stderr, "Error: no such room: %s\n", room_id.c_str());
@@ -169,7 +197,7 @@ namespace QuickMedia {
}
if(!room_it->second->initial_fetch_finished) {
- PluginResult result = load_initial_room_data(room_id, room_it->second.get());
+ PluginResult result = get_previous_room_messages(room_id, room_it->second.get());
if(result == PluginResult::OK) {
room_it->second->initial_fetch_finished = true;
} else {
@@ -178,36 +206,49 @@ namespace QuickMedia {
}
}
- // This will happen if there are no new messages
- if(start_index >= room_it->second->messages.size())
- return PluginResult::OK;
+ room_messages_to_body_items(room_it->second.get(), room_it->second->messages.data(), room_it->second->messages.size(), result_items);
+ room_it->second->last_read_index = room_it->second->messages.size();
+ return PluginResult::OK;
+ }
- num_new_messages = room_it->second->messages.size() - start_index;
+ PluginResult Matrix::get_new_room_messages(const std::string &room_id, BodyItems &result_items) {
+ auto room_it = room_data_by_id.find(room_id);
+ if(room_it == room_data_by_id.end()) {
+ fprintf(stderr, "Error: no such room: %s\n", room_id.c_str());
+ return PluginResult::ERR;
+ }
- size_t prev_user_id = -1;
- for(auto it = room_it->second->messages.begin() + start_index, end = room_it->second->messages.end(); it != end; ++it) {
- const UserInfo &user_info = room_it->second->user_info[it->user_id];
- if(it->user_id == prev_user_id && it->url.empty()) {
- assert(!result_items.empty());
- result_items.back()->append_description("\n");
- result_items.back()->append_description(it->body);
+ if(!room_it->second->initial_fetch_finished) {
+ PluginResult result = get_previous_room_messages(room_id, room_it->second.get());
+ if(result == PluginResult::OK) {
+ room_it->second->initial_fetch_finished = true;
} else {
- auto body_item = std::make_unique<BodyItem>("");
- body_item->set_author(user_info.display_name);
- body_item->set_description(it->body);
- if(!it->thumbnail_url.empty())
- body_item->thumbnail_url = it->thumbnail_url;
- else if(!it->url.empty())
- body_item->thumbnail_url = it->url;
- else
- body_item->thumbnail_url = user_info.avatar_url;
- // TODO: Show image thumbnail inline instead of url to image
- body_item->url = it->url;
- result_items.push_back(std::move(body_item));
- prev_user_id = it->user_id;
+ fprintf(stderr, "Initial sync failed for room: %s\n", room_id.c_str());
+ return result;
}
}
+ size_t num_new_messages = room_it->second->messages.size() - room_it->second->last_read_index;
+ room_messages_to_body_items(room_it->second.get(), room_it->second->messages.data() + room_it->second->last_read_index, num_new_messages, result_items);
+ room_it->second->last_read_index = room_it->second->messages.size();
+ return PluginResult::OK;
+ }
+
+ PluginResult Matrix::get_previous_room_messages(const std::string &room_id, BodyItems &result_items) {
+ auto room_it = room_data_by_id.find(room_id);
+ if(room_it == room_data_by_id.end()) {
+ fprintf(stderr, "Error: no such room: %s\n", room_id.c_str());
+ return PluginResult::ERR;
+ }
+
+ size_t num_messages_before = room_it->second->messages.size();
+ PluginResult result = get_previous_room_messages(room_id, room_it->second.get());
+ if(result != PluginResult::OK)
+ return result;
+
+ size_t num_messages_after = room_it->second->messages.size();
+ size_t num_new_messages = num_messages_after - num_messages_before;
+ room_messages_to_body_items(room_it->second.get(), room_it->second->messages.data(), num_new_messages, result_items);
return PluginResult::OK;
}
@@ -270,10 +311,12 @@ namespace QuickMedia {
if(!timeline_json.isObject())
continue;
- // This may be non-existent if this is the first event in the room
- const Json::Value &prev_batch_json = timeline_json["prev_batch"];
- if(prev_batch_json.isString())
- room_it->second->prev_batch = prev_batch_json.asString();
+ if(room_it->second->prev_batch.empty()) {
+ // This may be non-existent if this is the first event in the room
+ const Json::Value &prev_batch_json = timeline_json["prev_batch"];
+ if(prev_batch_json.isString())
+ room_it->second->prev_batch = prev_batch_json.asString();
+ }
const Json::Value &events_json = timeline_json["events"];
events_add_user_info(events_json, room_it->second.get());
@@ -415,8 +458,11 @@ namespace QuickMedia {
}
}
+ // TODO: Loop and std::move instead? doesn't insert create copies?
if(message_dir == MessageDirection::BEFORE) {
room_data->messages.insert(room_data->messages.begin(), new_messages.rbegin(), new_messages.rend());
+ if(room_data->last_read_index != 0)
+ room_data->last_read_index += new_messages.size();
} else if(message_dir == MessageDirection::AFTER) {
room_data->messages.insert(room_data->messages.end(), new_messages.begin(), new_messages.end());
}
@@ -500,7 +546,7 @@ namespace QuickMedia {
}
}
- PluginResult Matrix::load_initial_room_data(const std::string &room_id, RoomData *room_data) {
+ PluginResult Matrix::get_previous_room_messages(const std::string &room_id, RoomData *room_data) {
std::string from = room_data->prev_batch;
if(from.empty()) {
fprintf(stderr, "Info: missing previous batch for room: %s, using /sync next batch\n", room_id.c_str());
@@ -554,6 +600,13 @@ namespace QuickMedia {
const Json::Value &chunk_json = json_root["chunk"];
events_add_messages(chunk_json, room_data, MessageDirection::BEFORE);
+ const Json::Value &end_json = json_root["end"];
+ if(!end_json.isString()) {
+ fprintf(stderr, "Warning: matrix messages response is missing 'end', this could happen if we received the very first messages in the room\n");
+ return PluginResult::OK;
+ }
+
+ room_data->prev_batch = end_json.asString();
return PluginResult::OK;
}