aboutsummaryrefslogtreecommitdiff
path: root/src/QuickMedia.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/QuickMedia.cpp')
-rw-r--r--src/QuickMedia.cpp282
1 files changed, 201 insertions, 81 deletions
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index c01ce59..5c97c2d 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -3199,41 +3199,50 @@ namespace QuickMedia {
bool redraw = true;
// TODO: Optimize with hash map?
- auto find_body_item_by_event_id = [](std::shared_ptr<BodyItem> *body_items, size_t num_body_items, const std::string &event_id) -> std::shared_ptr<BodyItem> {
+ auto find_body_item_by_event_id = [](std::shared_ptr<BodyItem> *body_items, size_t num_body_items, const std::string &event_id, size_t *index_result = nullptr) -> std::shared_ptr<BodyItem> {
for(size_t i = 0; i < num_body_items; ++i) {
auto &body_item = body_items[i];
- if(static_cast<Message*>(body_item->userdata)->event_id == event_id)
+ if(static_cast<Message*>(body_item->userdata)->event_id == event_id) {
+ if(index_result)
+ *index_result = i;
return body_item;
+ }
}
return nullptr;
};
// TODO: What if these never end up referencing events? clean up automatically after a while?
- std::unordered_map<RoomData*, Messages> unreferenced_event_by_room;
+ Messages unreferenced_events;
auto set_body_as_deleted = [&current_room](Message *message, BodyItem *body_item) {
body_item->embedded_item = nullptr;
body_item->embedded_item_status = FetchStatus::NONE;
+ message->type = MessageType::REDACTION;
+ message->related_event_id.clear();
+ message->related_event_type = RelatedEventType::NONE;
body_item->thumbnail_url = current_room->get_user_avatar_url(message->user);
body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE;
+ body_item->set_description("Message deleted");
body_item->set_description_color(sf::Color::White);
body_item->thumbnail_size = sf::Vector2i(32, 32);
};
// TODO: Optimize with hash map?
- auto resolve_unreferenced_events_with_body_items = [&set_body_as_deleted, &unreferenced_event_by_room, &current_room, &find_body_item_by_event_id](std::shared_ptr<BodyItem> *body_items, size_t num_body_items) {
- auto &unreferenced_events = unreferenced_event_by_room[current_room];
+ auto resolve_unreferenced_events_with_body_items = [&set_body_as_deleted, &unreferenced_events, &find_body_item_by_event_id](std::shared_ptr<BodyItem> *body_items, size_t num_body_items) {
for(auto it = unreferenced_events.begin(); it != unreferenced_events.end(); ) {
auto &message = *it;
// TODO: Make redacted/edited events as (redacted)/(edited) in the body
if(message->related_event_type == RelatedEventType::REDACTION || message->related_event_type == RelatedEventType::EDIT) {
auto body_item = find_body_item_by_event_id(body_items, num_body_items, message->related_event_id);
if(body_item) {
- body_item->set_description(message_get_body_remove_formatting(message.get()));
// TODO: Append the new message to the body item so the body item should have a list of edit events
//body_item->userdata = message.get();
- if(message->related_event_type == RelatedEventType::REDACTION)
+ if(message->related_event_type == RelatedEventType::REDACTION) {
set_body_as_deleted(message.get(), body_item.get());
+ } else {
+ body_item->set_description(message_get_body_remove_formatting(message.get()));
+ body_item->set_description_color(sf::Color::White);
+ }
it = unreferenced_events.erase(it);
} else {
++it;
@@ -3245,22 +3254,24 @@ namespace QuickMedia {
};
// TODO: Optimize with hash map?
- auto modify_related_messages_in_current_room = [&set_body_as_deleted, &unreferenced_event_by_room, &current_room, &find_body_item_by_event_id, &tabs](Messages &messages) {
+ auto modify_related_messages_in_current_room = [&set_body_as_deleted, &unreferenced_events, &find_body_item_by_event_id, &tabs](Messages &messages) {
if(messages.empty())
return;
- auto &unreferenced_events = unreferenced_event_by_room[current_room];
auto &body_items = tabs[MESSAGES_TAB_INDEX].body->items;
for(auto &message : messages) {
// TODO: Make redacted/edited events as (redacted)/(edited) in the body
if(message->related_event_type == RelatedEventType::REDACTION || message->related_event_type == RelatedEventType::EDIT) {
auto body_item = find_body_item_by_event_id(body_items.data(), body_items.size(), message->related_event_id);
if(body_item) {
- body_item->set_description(message_get_body_remove_formatting(message.get()));
// TODO: Append the new message to the body item so the body item should have a list of edit events
//body_item->userdata = message.get();
- if(message->related_event_type == RelatedEventType::REDACTION)
+ if(message->related_event_type == RelatedEventType::REDACTION) {
set_body_as_deleted(message.get(), body_item.get());
+ } else {
+ body_item->set_description(message_get_body_remove_formatting(message.get()));
+ body_item->set_description_color(sf::Color::White);
+ }
} else {
unreferenced_events.push_back(message);
}
@@ -3350,18 +3361,64 @@ namespace QuickMedia {
chat_input.draw_background = false;
chat_input.set_editable(false);
- MessageQueue<std::function<void()>> post_task_queue;
- auto post_thread_handler = [&post_task_queue]() {
+ struct ProvisionalMessage {
+ std::shared_ptr<BodyItem> body_item;
+ std::shared_ptr<Message> message;
+ std::string event_id;
+ };
+
+ std::vector<ProvisionalMessage> unresolved_provisional_messages; // |event_id| is always empty in this. Use |message->event_id| instead
+ std::optional<ProvisionalMessage> provisional_message;
+ MessageQueue<ProvisionalMessage> provisional_message_queue;
+ MessageQueue<std::function<ProvisionalMessage()>> post_task_queue;
+ auto post_thread_handler = [&provisional_message_queue, &post_task_queue]() {
while(true) {
- std::optional<std::function<void()>> post_task_opt = post_task_queue.pop_wait();
+ std::optional<std::function<ProvisionalMessage()>> post_task_opt = post_task_queue.pop_wait();
if(!post_task_opt)
break;
- post_task_opt.value()();
+ provisional_message_queue.push(post_task_opt.value()());
}
};
std::thread post_thread(post_thread_handler);
- chat_input.on_submit_callback = [this, &chat_input, &selected_tab, &current_room, &new_page, &chat_state, &currently_operating_on_item, &post_task_queue](std::string text) mutable {
+ auto resolve_provisional_messages = [&unresolved_provisional_messages](Messages &messages) {
+ if(messages.empty() || unresolved_provisional_messages.empty())
+ return;
+
+ for(auto it = unresolved_provisional_messages.begin(); it != unresolved_provisional_messages.end();) {
+ auto find_it = std::find_if(messages.cbegin(), messages.cend(), [&it](const std::shared_ptr<Message> &message) {
+ return message->event_id == it->message->event_id;
+ });
+ if(find_it != messages.end()) {
+ it->body_item->set_description_color(sf::Color::White);
+ it->body_item->userdata = find_it->get(); // This is ok because the message is added to |all_messages|
+ it = unresolved_provisional_messages.erase(it);
+ messages.erase(find_it);
+ } else {
+ ++it;
+ }
+ }
+ };
+
+ auto upload_file = [this, &tabs, &current_room](const std::string &filepath) {
+ TaskResult post_file_result = run_task_with_loading_screen([this, &current_room, filepath]() {
+ std::string event_id_response;
+ std::string err_msg;
+ if(matrix->post_file(current_room, filepath, event_id_response, err_msg) == PluginResult::OK) {
+ return true;
+ } else {
+ show_notification("QuickMedia", "Failed to upload media to room, error: " + err_msg, Urgency::CRITICAL);
+ return false;
+ }
+ });
+
+ if(post_file_result == TaskResult::TRUE) {
+ if(tabs[MESSAGES_TAB_INDEX].body->is_last_item_fully_visible())
+ tabs[MESSAGES_TAB_INDEX].body->select_last_item();
+ }
+ };
+
+ chat_input.on_submit_callback = [this, &tabs, &me, &chat_input, &selected_tab, &current_room, &new_page, &chat_state, &currently_operating_on_item, &post_task_queue, &unreferenced_events, &find_body_item_by_event_id](std::string text) mutable {
if(!current_room)
return false;
@@ -3400,34 +3457,82 @@ namespace QuickMedia {
}
}
+ auto message = std::make_shared<Message>();
+ message->user = matrix->get_me(current_room);
+ message->body = text;
+ message->type = MessageType::TEXT;
+ message->timestamp = time(NULL) * 1000;
+
+ const sf::Color provisional_message_color(171, 175, 180);
+
+ int num_items = tabs[MESSAGES_TAB_INDEX].body->items.size();
+ bool scroll_to_end = num_items == 0;
+ if(tabs[MESSAGES_TAB_INDEX].body->is_selected_item_last_visible_item() && selected_tab == MESSAGES_TAB_INDEX)
+ scroll_to_end = true;
+
if(chat_state == ChatState::TYPING_MESSAGE) {
- post_task_queue.push([this, &current_room, text, msgtype]() {
- if(matrix->post_message(current_room, text, std::nullopt, std::nullopt, msgtype) != PluginResult::OK)
+ auto body_item = message_to_body_item(current_room, message.get(), current_room->get_user_avatar_url(me), me->user_id);
+ body_item->set_description_color(provisional_message_color);
+ tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps({body_item});
+ post_task_queue.push([this, &current_room, text, msgtype, body_item, message]() {
+ ProvisionalMessage provisional_message;
+ provisional_message.body_item = body_item;
+ provisional_message.message = message;
+ if(matrix->post_message(current_room, text, provisional_message.event_id, std::nullopt, std::nullopt, msgtype) != PluginResult::OK)
fprintf(stderr, "Failed to post matrix message\n");
+ return provisional_message;
});
chat_input.set_editable(false);
chat_state = ChatState::NAVIGATING;
+ if(scroll_to_end)
+ tabs[MESSAGES_TAB_INDEX].body->select_last_item();
return true;
} else if(chat_state == ChatState::REPLYING) {
void *related_to_message = currently_operating_on_item->userdata;
- post_task_queue.push([this, &current_room, text, related_to_message]() {
- if(matrix->post_reply(current_room, text, related_to_message) != PluginResult::OK)
+ message->related_event_type = RelatedEventType::REPLY;
+ message->related_event_id = static_cast<Message*>(related_to_message)->event_id;
+ auto body_item = message_to_body_item(current_room, message.get(), current_room->get_user_avatar_url(me), me->user_id);
+ body_item->set_description_color(provisional_message_color);
+ tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps({body_item});
+ post_task_queue.push([this, &current_room, text, related_to_message, body_item, message]() {
+ ProvisionalMessage provisional_message;
+ provisional_message.body_item = body_item;
+ provisional_message.message = message;
+ if(matrix->post_reply(current_room, text, related_to_message, provisional_message.event_id) != PluginResult::OK)
fprintf(stderr, "Failed to post matrix reply\n");
+ return provisional_message;
});
chat_input.set_editable(false);
chat_state = ChatState::NAVIGATING;
currently_operating_on_item = nullptr;
+ if(scroll_to_end)
+ tabs[MESSAGES_TAB_INDEX].body->select_last_item();
return true;
} else if(chat_state == ChatState::EDITING) {
void *related_to_message = currently_operating_on_item->userdata;
- post_task_queue.push([this, &current_room, text, related_to_message]() {
- if(matrix->post_edit(current_room, text, related_to_message) != PluginResult::OK)
- fprintf(stderr, "Failed to post matrix edit\n");
- });
- chat_input.set_editable(false);
- chat_state = ChatState::NAVIGATING;
- currently_operating_on_item = nullptr;
- return true;
+ message->related_event_type = RelatedEventType::EDIT;
+ message->related_event_id = static_cast<Message*>(related_to_message)->event_id;
+ auto body_item = find_body_item_by_event_id(tabs[MESSAGES_TAB_INDEX].body->items.data(), tabs[MESSAGES_TAB_INDEX].body->items.size(), message->related_event_id);
+ if(body_item) {
+ body_item->set_description(text);
+ body_item->set_description_color(provisional_message_color);
+ unreferenced_events.push_back(message);
+ post_task_queue.push([this, &current_room, text, related_to_message, message]() {
+ ProvisionalMessage provisional_message;
+ provisional_message.message = message;
+ if(matrix->post_edit(current_room, text, related_to_message, provisional_message.event_id) != PluginResult::OK)
+ fprintf(stderr, "Failed to post matrix edit\n");
+ return provisional_message;
+ });
+
+ chat_input.set_editable(false);
+ chat_state = ChatState::NAVIGATING;
+ currently_operating_on_item = nullptr;
+ return true;
+ } else {
+ show_notification("QuickMedia", "Failed to edit message. Message refers to a non-existing message", Urgency::CRITICAL);
+ return false;
+ }
}
}
return false;
@@ -3567,10 +3672,10 @@ namespace QuickMedia {
bool fetched_enough_messages = false;
bool initial_prev_messages_fetch = true;
- auto fetch_more_previous_messages_if_needed = [this, &all_messages, &current_room, &fetched_enough_messages, &previous_messages_future, &initial_prev_messages_fetch]() {
+ auto fetch_more_previous_messages_if_needed = [this, &tabs, &current_room, &fetched_enough_messages, &previous_messages_future, &initial_prev_messages_fetch]() {
if(!fetched_enough_messages && !previous_messages_future.ready()) {
bool fetch_latest_messages = !matrix->is_initial_sync_finished() && initial_prev_messages_fetch;
- if(all_messages.size() < 10) {
+ if(!tabs[MESSAGES_TAB_INDEX].body->is_body_full_with_items()) {
previous_messages_future = [this, &current_room, fetch_latest_messages]() {
Messages messages;
if(matrix->get_previous_room_messages(current_room, messages, fetch_latest_messages) != PluginResult::OK)
@@ -3794,7 +3899,7 @@ namespace QuickMedia {
}
};
- auto cleanup_tasks = [&set_read_marker_future, &fetch_message_future, &typing_state_queue, &typing_state_thread, &post_task_queue, &post_thread, &tabs]() {
+ auto cleanup_tasks = [&set_read_marker_future, &fetch_message_future, &typing_state_queue, &typing_state_thread, &post_task_queue, &provisional_message_queue, &post_thread, &tabs]() {
set_read_marker_future.cancel();
fetch_message_future.cancel();
typing_state_queue.close();
@@ -3807,6 +3912,7 @@ namespace QuickMedia {
program_kill_in_thread(post_thread.get_id());
post_thread.join();
}
+ provisional_message_queue.clear();
//unreferenced_event_by_room.clear();
@@ -3940,20 +4046,7 @@ namespace QuickMedia {
if(event.key.control && event.key.code == sf::Keyboard::V) {
frame_skip_text_entry = true;
// TODO: Upload multiple files.
- std::string selected_file = sf::Clipboard::getString();
- TaskResult post_file_result = run_task_with_loading_screen([this, &current_room, selected_file]() {
- std::string err_msg;
- if(matrix->post_file(current_room, selected_file, err_msg) == PluginResult::OK) {
- return true;
- } else {
- show_notification("QuickMedia", "Failed to upload media to room, error: " + err_msg, Urgency::CRITICAL);
- return false;
- }
- });
- if(post_file_result == TaskResult::TRUE) {
- if(tabs[MESSAGES_TAB_INDEX].body->is_last_item_fully_visible())
- tabs[MESSAGES_TAB_INDEX].body->select_last_item();
- }
+ upload_file(sf::Clipboard::getString());
}
if(event.key.code == sf::Keyboard::R) {
@@ -3961,10 +4054,15 @@ namespace QuickMedia {
std::shared_ptr<BodyItem> selected = tabs[selected_tab].body->get_selected_shared();
if(selected) {
if(!is_state_message_type(static_cast<Message*>(selected->userdata))) {
- chat_state = ChatState::REPLYING;
- currently_operating_on_item = selected;
- chat_input.set_editable(true);
- replying_to_text.setString("Replying to:");
+ if(static_cast<Message*>(selected->userdata)->event_id.empty()) {
+ // TODO: Show inline notification
+ show_notification("QuickMedia", "You can't reply to a message that hasn't been sent yet");
+ } else {
+ chat_state = ChatState::REPLYING;
+ currently_operating_on_item = selected;
+ chat_input.set_editable(true);
+ replying_to_text.setString("Replying to:");
+ }
}
} else {
// TODO: Show inline notification
@@ -3977,9 +4075,12 @@ namespace QuickMedia {
std::shared_ptr<BodyItem> selected = tabs[selected_tab].body->get_selected_shared();
if(selected) {
if(!is_state_message_type(static_cast<Message*>(selected->userdata))) {
- if(!selected->url.empty()) { // cant edit messages that are image/video posts
+ if(static_cast<Message*>(selected->userdata)->event_id.empty()) {
+ // TODO: Show inline notification
+ show_notification("QuickMedia", "You can't edit a message that hasn't been sent yet");
+ } else if(!selected->url.empty()) { // cant edit messages that are image/video posts
// TODO: Show inline notification
- show_notification("QuickMedia", "You can only edit messages with no file attached to it");
+ show_notification("QuickMedia", "You can't edit messages with files attached to them");
} else if(!matrix->was_message_posted_by_me(selected->userdata)) {
// TODO: Show inline notification
show_notification("QuickMedia", "You can't edit a message that was posted by somebody else");
@@ -4003,14 +4104,22 @@ namespace QuickMedia {
BodyItem *selected = tabs[selected_tab].body->get_selected();
if(selected) {
if(!is_state_message_type(static_cast<Message*>(selected->userdata))) {
- void *selected_message = selected->userdata;
- post_task_queue.push([this, &current_room, selected_message]() {
- std::string err_msg;
- if(matrix->delete_message(current_room, selected_message, err_msg) != PluginResult::OK) {
- // TODO: Show inline notification
- fprintf(stderr, "Failed to delete message, reason: %s\n", err_msg.c_str());
- }
- });
+ if(static_cast<Message*>(selected->userdata)->event_id.empty()) {
+ // TODO: Show inline notification
+ show_notification("QuickMedia", "You can't delete a message that hasn't been sent yet");
+ } else {
+ set_body_as_deleted(static_cast<Message*>(selected->userdata), selected);
+ void *selected_message = selected->userdata;
+ post_task_queue.push([this, &current_room, selected_message]() {
+ ProvisionalMessage provisional_message;
+ std::string err_msg;
+ if(matrix->delete_message(current_room, selected_message, err_msg) != PluginResult::OK) {
+ // TODO: Show inline notification
+ fprintf(stderr, "Failed to delete message, reason: %s\n", err_msg.c_str());
+ }
+ return provisional_message;
+ });
+ }
}
} else {
// TODO: Show inline notification
@@ -4106,19 +4215,7 @@ namespace QuickMedia {
fprintf(stderr, "No files selected!\n");
} else {
// TODO: Upload multiple files.
- TaskResult post_file_result = run_task_with_loading_screen([this, &current_room]() {
- std::string err_msg;
- if(matrix->post_file(current_room, selected_files[0], err_msg) == PluginResult::OK) {
- return true;
- } else {
- show_notification("QuickMedia", "Failed to upload media to room, error: " + err_msg, Urgency::CRITICAL);
- return false;
- }
- });
- if(post_file_result == TaskResult::TRUE) {
- if(tabs[MESSAGES_TAB_INDEX].body->is_last_item_fully_visible())
- tabs[MESSAGES_TAB_INDEX].body->select_last_item();
- }
+ upload_file(selected_files[0]);
}
redraw = true;
}
@@ -4128,7 +4225,7 @@ namespace QuickMedia {
previous_messages_future.cancel();
cleanup_tasks();
tabs.clear();
- unreferenced_event_by_room.clear();
+ unreferenced_events.clear();
all_messages.clear();
new_page = PageType::CHAT;
matrix->stop_sync();
@@ -4223,10 +4320,25 @@ namespace QuickMedia {
logo_sprite.setPosition(logo_padding_x, std::floor(window_size.y - chat_input_shade.getSize().y * 0.5f - logo_size.y * 0.5f));
}
+ while((provisional_message = provisional_message_queue.pop_if_available()) != std::nullopt) {
+ if(!provisional_message->body_item || !provisional_message->message)
+ continue;
+
+ if(!provisional_message->event_id.empty()) {
+ provisional_message->message->event_id = std::move(provisional_message->event_id);
+ unresolved_provisional_messages.push_back(provisional_message.value());
+ } else if(provisional_message->body_item) {
+ provisional_message->body_item->set_description("Failed to send: " + provisional_message->body_item->get_description());
+ provisional_message->body_item->set_description_color(sf::Color::Red);
+ }
+ }
+
sync_data.messages.clear();
+ sync_data.pinned_events = std::nullopt;
matrix->get_room_sync_data(current_room, sync_data);
if(!sync_data.messages.empty()) {
all_messages.insert(all_messages.end(), sync_data.messages.begin(), sync_data.messages.end());
+ resolve_provisional_messages(sync_data.messages);
filter_existing_messages(sync_data.messages);
}
add_new_messages_to_current_room(sync_data.messages);
@@ -4242,7 +4354,7 @@ namespace QuickMedia {
if(previous_messages_future.ready()) {
Messages new_messages = previous_messages_future.get();
all_messages.insert(all_messages.end(), new_messages.begin(), new_messages.end());
- if(new_messages.empty() || all_messages.size() >= 10)
+ if(new_messages.empty())
fetched_enough_messages = true;
filter_existing_messages(new_messages);
fprintf(stderr, "Finished fetching older messages, num new messages: %zu\n", new_messages.size());
@@ -4254,7 +4366,7 @@ namespace QuickMedia {
BodyItem *selected_item = tabs[MESSAGES_TAB_INDEX].body->get_selected();
BodyItems new_body_items = messages_to_body_items(current_room, new_messages, current_room->get_user_display_name(me), me->user_id);
size_t num_new_body_items = new_body_items.size();
- tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps(std::move(new_body_items));
+ tabs[MESSAGES_TAB_INDEX].body->prepend_items(std::move(new_body_items)); // TODO: Insert by timestamp? then make sure num_new_body_items matches the first items blabla
if(selected_item) {
int selected_item_index = tabs[MESSAGES_TAB_INDEX].body->get_index_by_body_item(selected_item);
if(selected_item_index != -1)
@@ -4430,7 +4542,7 @@ namespace QuickMedia {
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;
// TODO: What if two messages have the same timestamp?
- if(message->timestamp > current_room->last_read_message_timestamp) {
+ if(message && !message->event_id.empty() && message->timestamp > current_room->last_read_message_timestamp) {
//read_marker_timeout_ms = read_marker_timeout_ms_default;
current_room->last_read_message_timestamp = message->timestamp;
// TODO: What if the message is no longer valid?
@@ -4464,7 +4576,6 @@ namespace QuickMedia {
if(matrix_chat_page->should_clear_data) {
matrix_chat_page->should_clear_data = false;
- cleanup_tasks();
std::string err_msg;
while(!matrix->is_initial_sync_finished()) {
@@ -4478,13 +4589,22 @@ namespace QuickMedia {
current_room = matrix->get_room_by_id(current_room->id);
if(current_room) {
+ all_messages.clear();
+ tabs[MESSAGES_TAB_INDEX].body->clear_items();
+
+ matrix->get_all_synced_room_messages(current_room, all_messages);
+ for(auto &message : all_messages) {
+ fetched_messages_set.insert(message->event_id);
+ }
+ auto me = matrix->get_me(current_room);
+ resolve_provisional_messages(all_messages);
+ add_new_messages_to_current_room(all_messages);
+ modify_related_messages_in_current_room(all_messages);
+
std::vector<std::string> pinned_events;
matrix->get_all_pinned_events(current_room, pinned_events);
process_pinned_events(std::move(pinned_events));
- typing_state_queue.restart();
- typing_state_thread = std::thread(typing_state_handler);
- post_task_queue.restart();
- post_thread = std::thread(post_thread_handler);
+
fetch_more_previous_messages_if_needed();
} else {
go_to_previous_page = true;