aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2020-11-01 12:19:30 +0100
committerdec05eba <dec05eba@protonmail.com>2020-11-01 12:19:30 +0100
commit7292fe11254109266e7adb9937e5f0fa797711f6 (patch)
treea581c05e0f0b795646fb1458ce118adf5bb5bd88 /src
parent2278d52b59e9818f08bd73ec6583a1589d9512f2 (diff)
Matrix: add invites tab, add /leave command, remove room when leaving, add async loading for more tasks
Diffstat (limited to 'src')
-rw-r--r--src/QuickMedia.cpp68
-rw-r--r--src/plugins/Matrix.cpp598
2 files changed, 532 insertions, 134 deletions
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index dc3d167..e6d7b9a 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -353,6 +353,8 @@ namespace QuickMedia {
if(create_directory_recursive(get_cache_dir().join("thumbnails")) != 0) {
fprintf(stderr, "Failed to create thumbnails directory\n");
}
+
+ main_thread_id = std::this_thread::get_id();
}
Program::~Program() {
@@ -586,22 +588,19 @@ namespace QuickMedia {
if(matrix) {
matrix->use_tor = use_tor;
- {
- auto window_size_u = window.getSize();
- window_size.x = window_size_u.x;
- window_size.y = window_size_u.y;
- sf::Text loading_text("Loading...", *FontLoader::get_font(FontLoader::FontType::LATIN), 24);
- loading_text.setPosition(window_size.x * 0.5f - loading_text.getLocalBounds().width * 0.5f, window_size.y * 0.5f - loading_text.getLocalBounds().height * 0.5f);
- window.clear(back_color);
- window.draw(loading_text);
- window.display();
- }
- if(matrix->load_and_verify_cached_session() == PluginResult::OK) {
+
+ TaskResult task_result = run_task_with_loading_screen([this]() {
+ return matrix->load_and_verify_cached_session() == PluginResult::OK;
+ });
+
+ if(task_result == TaskResult::TRUE) {
current_page = PageType::CHAT;
- } else {
+ } else if(task_result == TaskResult::FALSE) {
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();
@@ -882,6 +881,10 @@ namespace QuickMedia {
return current_chat_room;
}
+ void Program::set_go_to_previous_page() {
+ go_to_previous_page = true;
+ }
+
static void select_body_item_by_room(Body *body, RoomData *room) {
for(size_t i = 0; i < body->items.size(); ++i) {
auto &body_item = body->items[i];
@@ -1327,6 +1330,11 @@ namespace QuickMedia {
}
window.display();
+
+ if(go_to_previous_page) {
+ go_to_previous_page = false;
+ goto page_end;
+ }
}
page_end:
@@ -1500,6 +1508,8 @@ namespace QuickMedia {
}
TaskResult Program::run_task_with_loading_screen(std::function<bool()> callback) {
+ assert(std::this_thread::get_id() == main_thread_id);
+
std::promise<bool> result_promise;
std::future<bool> future = result_promise.get_future();
std::thread task_thread([](std::promise<bool> &&promise, std::function<bool()> callback) {
@@ -1642,7 +1652,7 @@ namespace QuickMedia {
if(err != VideoPlayer::Error::OK) {
std::string err_msg = "Failed to play url: ";
err_msg += video_url;
- show_notification("Video player", err_msg.c_str(), Urgency::CRITICAL);
+ show_notification("QuickMedia", err_msg.c_str(), Urgency::CRITICAL);
current_page = previous_page;
} else {
related_media_body->clear_items();
@@ -1658,7 +1668,7 @@ namespace QuickMedia {
std::string err_msg = "Failed to extract id of youtube url ";
err_msg += video_url;
err_msg + ", video wont be saved in history";
- show_notification("Video player", err_msg.c_str(), Urgency::LOW);
+ show_notification("QuickMedia", err_msg.c_str(), Urgency::LOW);
return;
}
@@ -1718,7 +1728,7 @@ namespace QuickMedia {
// If there are no videos to play, then dont play any...
if(new_video_url.empty()) {
- show_notification("Video player", "No more related videos to play");
+ show_notification("QuickMedia", "No more related videos to play");
current_page = previous_page;
return;
}
@@ -1865,7 +1875,7 @@ namespace QuickMedia {
VideoPlayer::Error update_err = video_player->update();
if(update_err == VideoPlayer::Error::FAIL_TO_CONNECT_TIMEOUT) {
- show_notification("Video player", "Failed to connect to mpv ipc after 10 seconds", Urgency::CRITICAL);
+ show_notification("QuickMedia", "Failed to connect to mpv ipc after 10 seconds", Urgency::CRITICAL);
current_page = previous_page;
break;
} else if(update_err == VideoPlayer::Error::EXITED && video_player->exit_status == 0) {
@@ -1873,7 +1883,7 @@ namespace QuickMedia {
current_page = previous_page;
break;
} else if(update_err != VideoPlayer::Error::OK) {
- show_notification("Video player", "The video player failed to play the video (error code " + std::to_string((int)update_err) + ")", Urgency::CRITICAL);
+ show_notification("QuickMedia", "The video player failed to play the video (error code " + std::to_string((int)update_err) + ")", Urgency::CRITICAL);
current_page = previous_page;
break;
}
@@ -3278,6 +3288,16 @@ namespace QuickMedia {
chat_input.set_editable(false);
chat_state = ChatState::NAVIGATING;
return true;
+ } else if(text == "/leave") {
+ TaskResult task_result = run_task_with_loading_screen([this, &current_room]() {
+ return matrix->leave_room(current_room->id) == PluginResult::OK;
+ });
+ if(task_result != TaskResult::FALSE) {
+ go_to_previous_page = true;
+ chat_input.set_editable(false);
+ chat_state = ChatState::NAVIGATING;
+ }
+ return true;
} else if(strncmp(text.c_str(), "/me ", 4) == 0) {
msgtype = "m.emote";
text.erase(text.begin(), text.begin() + 4);
@@ -4140,6 +4160,11 @@ namespace QuickMedia {
}
window.display();
+
+ if(go_to_previous_page) {
+ go_to_previous_page = false;
+ goto chat_page_end;
+ }
}
chat_page_end:
@@ -4172,12 +4197,17 @@ namespace QuickMedia {
rooms_tags_body->thumbnail_mask_shader = &circle_mask_shader;
auto matrix_rooms_tag_page = std::make_unique<MatrixRoomTagsPage>(this, rooms_tags_body.get());
- MatrixQuickMedia matrix_handler(this, matrix, matrix_rooms_page.get(), matrix_rooms_tag_page.get());
+ auto invites_body = create_body();
+ invites_body->thumbnail_mask_shader = &circle_mask_shader;
+ auto matrix_invites_page = std::make_unique<MatrixInvitesPage>(this, matrix, invites_body.get());
+
+ MatrixQuickMedia matrix_handler(this, matrix, matrix_rooms_page.get(), matrix_rooms_tag_page.get(), matrix_invites_page.get());
matrix->start_sync(&matrix_handler);
std::vector<Tab> tabs;
tabs.push_back(Tab{std::move(rooms_body), std::move(matrix_rooms_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
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();
@@ -4194,6 +4224,8 @@ namespace QuickMedia {
window_size.y = event.size.height;
sf::FloatRect visible_area(0, 0, window_size.x, window_size.y);
window.setView(sf::View(visible_area));
+ } else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape) {
+ window.close();
}
}
window.clear(back_color);
diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp
index 93eb1d6..b8c2f89 100644
--- a/src/plugins/Matrix.cpp
+++ b/src/plugins/Matrix.cpp
@@ -70,6 +70,15 @@ static std::string extract_first_line_elipses(const std::string &str, size_t max
}
namespace QuickMedia {
+ static void remove_body_item_by_url(BodyItems &body_items, const std::string &url) {
+ for(auto it = body_items.begin(); it != body_items.end();) {
+ if((*it)->url == url)
+ it = body_items.erase(it);
+ else
+ ++it;
+ }
+ }
+
std::shared_ptr<UserInfo> RoomData::get_user_by_id(const std::string &user_id) {
std::lock_guard<std::mutex> lock(room_mutex);
auto user_it = user_info_by_user_id.find(user_id);
@@ -203,12 +212,24 @@ namespace QuickMedia {
return tags;
}
- MatrixQuickMedia::MatrixQuickMedia(Program *program, Matrix *matrix, MatrixRoomsPage *rooms_page, MatrixRoomTagsPage *room_tags_page) : program(program), matrix(matrix), rooms_page(rooms_page), room_tags_page(room_tags_page) {
+ void RoomData::clear_data() {
+ std::lock_guard<std::mutex> lock(room_mutex);
+ fetched_messages_by_event_id.clear();
+ user_info_by_user_id.clear();
+ messages.clear();
+ message_by_event_id.clear();
+ pinned_events.clear();
+ tags.clear();
+ }
+
+ MatrixQuickMedia::MatrixQuickMedia(Program *program, Matrix *matrix, MatrixRoomsPage *rooms_page, MatrixRoomTagsPage *room_tags_page, MatrixInvitesPage *invites_page) :
+ program(program), matrix(matrix), rooms_page(rooms_page), room_tags_page(room_tags_page), invites_page(invites_page)
+ {
rooms_page->matrix_delegate = this;
room_tags_page->matrix_delegate = this;
}
- void MatrixQuickMedia::room_create(RoomData *room) {
+ void MatrixQuickMedia::join_room(RoomData *room) {
std::string room_name = room->get_name();
if(room_name.empty())
room_name = room->id;
@@ -220,11 +241,18 @@ namespace QuickMedia {
body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE;
body_item->thumbnail_size = sf::Vector2i(32, 32);
room->userdata = body_item.get();
- room_body_items.push_back(body_item);
rooms_page->add_body_item(body_item);
room_body_item_by_room[room] = body_item;
}
+ void MatrixQuickMedia::leave_room(RoomData *room, LeaveType leave_type, const std::string &reason) {
+ 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);
+ if(leave_type != LeaveType::LEAVE)
+ show_notification("QuickMedia", reason);
+ }
+
void MatrixQuickMedia::room_add_tag(RoomData *room, const std::string &tag) {
room_tags_page->add_room_body_item_to_tag(room_body_item_by_room[room], tag);
}
@@ -240,6 +268,23 @@ namespace QuickMedia {
room_messages_data.is_initial_sync = is_initial_sync;
}
+ void MatrixQuickMedia::add_invite(const std::string &room_id, const Invite &invite) {
+ auto body_item = BodyItem::create(invite.room_name);
+ body_item->set_description("Invited by " + invite.invited_by->display_name + " (" + invite.invited_by->user_id + ")");
+ body_item->url = room_id;
+ body_item->thumbnail_url = invite.room_avatar_url;
+ body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE;
+ body_item->thumbnail_size = sf::Vector2i(32, 32);
+ body_item->set_timestamp(invite.timestamp);
+ invites_page->add_body_item(std::move(body_item));
+ if(invite.new_invite)
+ show_notification("QuickMedia matrix - " + invite.room_name, "You were invited to " + invite.room_name + " by " + invite.invited_by->display_name + " (" + invite.invited_by->user_id + ")");
+ }
+
+ void MatrixQuickMedia::remove_invite(const std::string &room_id) {
+ invites_page->remove_body_item_by_room_id(room_id);
+ }
+
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];
@@ -269,6 +314,10 @@ namespace QuickMedia {
}
void MatrixQuickMedia::update(MatrixPageType page_type) {
+ update_pending_room_messages(page_type);
+ }
+
+ 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();
RoomData *current_room = program->get_current_chat_room();
@@ -337,17 +386,17 @@ namespace QuickMedia {
MatrixRoomsPage::MatrixRoomsPage(Program *program, Body *body, std::string title, MatrixRoomTagsPage *room_tags_page) : Page(program), body(body), title(std::move(title)), room_tags_page(room_tags_page) {
if(room_tags_page)
- room_tags_page->current_rooms_page = this;
+ room_tags_page->set_current_rooms_page(this);
}
MatrixRoomsPage::~MatrixRoomsPage() {
if(room_tags_page)
- room_tags_page->current_rooms_page = nullptr;
+ room_tags_page->set_current_rooms_page(nullptr);
}
PluginResult MatrixRoomsPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
(void)title;
- auto chat_page = std::make_unique<MatrixChatPage>(program, url);
+ auto chat_page = std::make_unique<MatrixChatPage>(program, url, this);
chat_page->matrix_delegate = matrix_delegate;
result_tabs.push_back(Tab{nullptr, std::move(chat_page), nullptr});
return PluginResult::OK;
@@ -356,6 +405,17 @@ namespace QuickMedia {
void MatrixRoomsPage::update() {
{
std::lock_guard<std::mutex> lock(mutex);
+ 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
+ if(current_chat_page && current_chat_page->room_id == room_id) {
+ program->set_go_to_previous_page();
+ body->select_first_item();
+ current_chat_page = nullptr;
+ }
+ }
+ pending_remove_body_items.clear();
+ body->clamp_selection();
body->append_items(std::move(room_body_items));
}
matrix_delegate->update(MatrixPageType::ROOM_LIST);
@@ -389,9 +449,19 @@ namespace QuickMedia {
}
}
+ void MatrixRoomsPage::remove_body_item_by_room_id(const std::string &room_id) {
+ std::lock_guard<std::mutex> lock(mutex);
+ pending_remove_body_items.push_back(room_id);
+ }
+
+ void MatrixRoomsPage::set_current_chat_page(MatrixChatPage *chat_page) {
+ std::lock_guard<std::mutex> lock(mutex);
+ current_chat_page = chat_page;
+ }
+
PluginResult MatrixRoomTagsPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
(void)title;
- std::lock_guard<std::mutex> lock(mutex);
+ std::lock_guard<std::recursive_mutex> lock(mutex);
auto body = create_body();
Body *body_ptr = body.get();
TagData &tag_data = tag_body_items_by_name[url];
@@ -406,7 +476,7 @@ namespace QuickMedia {
// TODO: Also add/remove body items to above body (in submit)
void MatrixRoomTagsPage::update() {
{
- std::lock_guard<std::mutex> lock(mutex);
+ std::lock_guard<std::recursive_mutex> lock(mutex);
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())
@@ -455,22 +525,116 @@ namespace QuickMedia {
}
void MatrixRoomTagsPage::add_room_body_item_to_tag(std::shared_ptr<BodyItem> body_item, const std::string &tag) {
- std::lock_guard<std::mutex> lock(mutex);
+ std::lock_guard<std::recursive_mutex> lock(mutex);
add_room_body_items_by_tags[tag].push_back(body_item);
}
void MatrixRoomTagsPage::remove_room_body_item_from_tag(std::shared_ptr<BodyItem> body_item, const std::string &tag) {
- std::lock_guard<std::mutex> lock(mutex);
+ std::lock_guard<std::recursive_mutex> lock(mutex);
remove_room_body_items_by_tags[tag].push_back(body_item);
}
void MatrixRoomTagsPage::move_room_to_top(RoomData *room) {
+ std::lock_guard<std::recursive_mutex> lock(mutex);
if(current_rooms_page)
current_rooms_page->move_room_to_top(room);
}
+ void MatrixRoomTagsPage::remove_body_item_by_room_id(const std::string &room_id) {
+ std::lock_guard<std::recursive_mutex> lock(mutex);
+ for(auto it = tag_body_items_by_name.begin(); it != tag_body_items_by_name.end();) {
+ remove_body_item_by_url(it->second.room_body_items, room_id);
+ if(it->second.room_body_items.empty())
+ it = tag_body_items_by_name.erase(it);
+ else
+ ++it;
+ }
+ if(current_rooms_page)
+ current_rooms_page->remove_body_item_by_room_id(room_id);
+ }
+
+ void MatrixRoomTagsPage::set_current_rooms_page(MatrixRoomsPage *rooms_page) {
+ std::lock_guard<std::recursive_mutex> lock(mutex);
+ current_rooms_page = rooms_page;
+ }
+
+ MatrixInvitesPage::MatrixInvitesPage(Program *program, Matrix *matrix, Body *body) : Page(program), matrix(matrix), body(body) {
+
+ }
+
+ PluginResult MatrixInvitesPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
+ auto body = create_body();
+ body->items.push_back(BodyItem::create("Accept"));
+ body->items.push_back(BodyItem::create("Decline"));
+ result_tabs.push_back(Tab{std::move(body), std::make_unique<MatrixInviteDetailsPage>(program, matrix, this, url, "Invite to " + title), nullptr});
+ return PluginResult::OK;
+ }
+
+ PluginResult MatrixInviteDetailsPage::submit(const std::string &title, const std::string&, std::vector<Tab>&) {
+ TaskResult task_result = program->run_task_with_loading_screen([this, title]() {
+ if(title == "Accept")
+ return matrix->join_room(room_id) == PluginResult::OK;
+ else if(title == "Decline")
+ return matrix->leave_room(room_id) == PluginResult::OK;
+ return false;
+ });
+
+ if(task_result == TaskResult::TRUE) {
+ invites_page->remove_body_item_by_room_id(room_id);
+ } else if(task_result == TaskResult::FALSE) {
+ std::string action_str;
+ if(title == "Accept")
+ action_str = "accept";
+ else if(title == "Decline")
+ action_str = "decline";
+ show_notification("QuickMedia", "Failed to " + action_str + " the room invite", Urgency::CRITICAL);
+ }
+
+ program->set_go_to_previous_page();
+ return PluginResult::OK;
+ }
+
+ void MatrixInvitesPage::update() {
+ std::lock_guard<std::mutex> lock(mutex);
+
+ for(const std::string &room_id : pending_remove_body_items) {
+ remove_body_item_by_url(body->items, room_id);
+ }
+ pending_remove_body_items.clear();
+ body->clamp_selection();
+
+ // TODO: Insert in reverse order (to show the latest invite at the top?)
+ body->insert_items_by_timestamps(std::move(body_items));
+ if(body->items.size() != prev_invite_count) {
+ prev_invite_count = body->items.size();
+ title = "Invites (" + std::to_string(body->items.size()) + ")";
+ }
+ }
+
+ void MatrixInvitesPage::add_body_item(std::shared_ptr<BodyItem> body_item) {
+ std::lock_guard<std::mutex> lock(mutex);
+ body_items.push_back(std::move(body_item));
+ }
+
+ void MatrixInvitesPage::remove_body_item_by_room_id(const std::string &room_id) {
+ std::lock_guard<std::mutex> lock(mutex);
+ pending_remove_body_items.push_back(room_id);
+ }
+
+ 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);
+ }
+
+ MatrixChatPage::~MatrixChatPage() {
+ if(rooms_page)
+ rooms_page->set_current_chat_page(nullptr);
+ }
+
void MatrixChatPage::update() {
matrix_delegate->update(MatrixPageType::CHAT);
+ if(rooms_page)
+ rooms_page->update();
}
void Matrix::start_sync(MatrixDelegate *delegate) {
@@ -641,94 +805,106 @@ namespace QuickMedia {
return PluginResult::OK;
const rapidjson::Value &join_json = GetMember(rooms_json, "join");
- if(!join_json.IsObject())
- return PluginResult::OK;
+ if(join_json.IsObject()) {
+ for(auto const &it : join_json.GetObject()) {
+ if(!it.value.IsObject())
+ continue;
- for(auto const &it : join_json.GetObject()) {
- if(!it.value.IsObject())
- continue;
+ const rapidjson::Value &room_id = it.name;
+ if(!room_id.IsString())
+ continue;
- const rapidjson::Value &room_id = it.name;
- if(!room_id.IsString())
- continue;
+ std::string room_id_str = room_id.GetString();
- std::string room_id_str = room_id.GetString();
+ bool is_new_room = false;
+ RoomData *room = get_room_by_id(room_id_str);
+ if(!room) {
+ auto new_room = std::make_unique<RoomData>();
+ new_room->id = room_id_str;
+ room = new_room.get();
+ add_room(std::move(new_room));
+ is_new_room = true;
+ }
- bool is_new_room = false;
- RoomData *room = get_room_by_id(room_id_str);
- if(!room) {
- auto new_room = std::make_unique<RoomData>();
- new_room->id = room_id_str;
- room = new_room.get();
- add_room(std::move(new_room));
- is_new_room = true;
- }
+ const rapidjson::Value &state_json = GetMember(it.value, "state");
+ if(state_json.IsObject()) {
+ const rapidjson::Value &events_json = GetMember(state_json, "events");
+ events_add_user_info(events_json, room);
+ events_set_room_name(events_json, room);
+ events_add_pinned_events(events_json, room);
+ }
- const rapidjson::Value &state_json = GetMember(it.value, "state");
- if(state_json.IsObject()) {
- const rapidjson::Value &events_json = GetMember(state_json, "events");
- events_add_user_info(events_json, room);
- events_set_room_name(events_json, room);
- events_add_pinned_events(events_json, room);
- }
+ const rapidjson::Value &ephemeral_json = GetMember(it.value, "ephemeral");
- const rapidjson::Value &ephemeral_json = GetMember(it.value, "ephemeral");
+ const rapidjson::Value &timeline_json = GetMember(it.value, "timeline");
+ if(timeline_json.IsObject()) {
+ if(!room->has_prev_batch()) {
+ // This may be non-existent if this is the first event in the room
+ const rapidjson::Value &prev_batch_json = GetMember(timeline_json, "prev_batch");
+ if(prev_batch_json.IsString())
+ room->set_prev_batch(prev_batch_json.GetString());
+ }
- const rapidjson::Value &timeline_json = GetMember(it.value, "timeline");
- if(timeline_json.IsObject()) {
- if(!room->has_prev_batch()) {
- // This may be non-existent if this is the first event in the room
- const rapidjson::Value &prev_batch_json = GetMember(timeline_json, "prev_batch");
- if(prev_batch_json.IsString())
- room->set_prev_batch(prev_batch_json.GetString());
- }
+ // 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()) {
+ const rapidjson::Value &highlight_count_json = GetMember(unread_notification_json, "highlight_count");
+ if(highlight_count_json.IsNumber() && highlight_count_json.GetInt64() > 0)
+ has_unread_notifications = true;
+ }
- // 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()) {
- const rapidjson::Value &highlight_count_json = GetMember(unread_notification_json, "highlight_count");
- if(highlight_count_json.IsNumber() && highlight_count_json.GetInt64() > 0)
- 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);
+ }
+ events_add_messages(events_json, room, MessageDirection::AFTER, delegate, 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);
+ }
}
- 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);
+ auto invites_it = invites.find(room_id_str);
+ if(invites_it != invites.end()) {
+ // TODO: Show leave type and reason and who caused the invite to be removed
+ delegate->remove_invite(room_id_str);
+ invites.erase(invites_it);
}
- events_add_messages(events_json, room, MessageDirection::AFTER, delegate, 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(is_new_room)
- delegate->room_create(room);
+ 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);
- }
-
- if(is_new_room) {
- room->acquire_room_lock();
- std::set<std::string> &room_tags = room->get_tags_unsafe();
- if(room_tags.empty()) {
- room_tags.insert(OTHERS_ROOM_TAG);
- delegate->room_add_tag(room, OTHERS_ROOM_TAG);
+ 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);
+ }
+
+ if(is_new_room) {
+ room->acquire_room_lock();
+ std::set<std::string> &room_tags = room->get_tags_unsafe();
+ if(room_tags.empty()) {
+ room_tags.insert(OTHERS_ROOM_TAG);
+ delegate->room_add_tag(room, OTHERS_ROOM_TAG);
+ }
+ room->release_room_lock();
}
- room->release_room_lock();
}
}
+ const rapidjson::Value &leave_json = GetMember(rooms_json, "leave");
+ remove_rooms(leave_json, delegate);
+
+ const rapidjson::Value &invite_json = GetMember(rooms_json, "invite");
+ add_invites(invite_json, delegate);
+
return PluginResult::OK;
}
@@ -1195,6 +1371,26 @@ namespace QuickMedia {
room_data->set_name(name_json.GetString());
}
+ for(const rapidjson::Value &event_item_json : events_json.GetArray()) {
+ if(!event_item_json.IsObject())
+ continue;
+
+ const rapidjson::Value &type_json = GetMember(event_item_json, "type");
+ if(!type_json.IsString() || strcmp(type_json.GetString(), "m.room.avatar") != 0)
+ continue;
+
+ const rapidjson::Value &content_json = GetMember(event_item_json, "content");
+ if(!content_json.IsObject())
+ continue;
+
+ const rapidjson::Value &url_json = GetMember(content_json, "url");
+ if(!url_json.IsString() || strncmp(url_json.GetString(), "mxc://", 6) != 0)
+ continue;
+
+ std::string url_json_str = url_json.GetString() + 6;
+ room_data->set_avatar_url(homeserver + "/_matrix/media/r0/thumbnail/" + std::move(url_json_str) + "?width=32&height=32&method=crop");
+ }
+
bool has_room_name = room_data->has_name();
bool has_room_avatar_url = room_data->has_avatar_url();
@@ -1235,26 +1431,6 @@ namespace QuickMedia {
has_room_avatar_url = true;
}
}
-
- for(const rapidjson::Value &event_item_json : events_json.GetArray()) {
- if(!event_item_json.IsObject())
- continue;
-
- const rapidjson::Value &type_json = GetMember(event_item_json, "type");
- if(!type_json.IsString() || strcmp(type_json.GetString(), "m.room.avatar") != 0)
- continue;
-
- const rapidjson::Value &content_json = GetMember(event_item_json, "content");
- if(!content_json.IsObject())
- continue;
-
- const rapidjson::Value &url_json = GetMember(content_json, "url");
- if(!url_json.IsString() || strncmp(url_json.GetString(), "mxc://", 6) != 0)
- continue;
-
- std::string url_json_str = url_json.GetString() + 6;
- room_data->set_avatar_url(homeserver + "/_matrix/media/r0/thumbnail/" + std::move(url_json_str) + "?width=32&height=32&method=crop");
- }
}
void Matrix::events_add_pinned_events(const rapidjson::Value &events_json, RoomData *room_data) {
@@ -1357,10 +1533,165 @@ namespace QuickMedia {
}
}
+ void Matrix::add_invites(const rapidjson::Value &invite_json, MatrixDelegate *delegate) {
+ if(!invite_json.IsObject())
+ return;
+
+ for(auto const &it : invite_json.GetObject()) {
+ if(!it.value.IsObject())
+ continue;
+
+ const rapidjson::Value &room_id = it.name;
+ if(!room_id.IsString())
+ continue;
+
+ const rapidjson::Value &invite_state_json = GetMember(it.value, "invite_state");
+ if(!invite_state_json.IsObject())
+ continue;
+
+ const rapidjson::Value &events_json = GetMember(invite_state_json, "events");
+ if(!events_json.IsArray())
+ continue;
+
+ 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())
+ continue;
+
+ if(strcmp(type_json.GetString(), "m.room.member") != 0)
+ continue;
+
+ const rapidjson::Value &content_json = GetMember(event_json, "content");
+ if(!content_json.IsObject())
+ continue;
+
+ const rapidjson::Value &sender_json = GetMember(event_json, "sender");
+ if(!sender_json.IsString())
+ continue;
+
+ const rapidjson::Value &timestamp_json = GetMember(event_json, "origin_server_ts");
+ if(!timestamp_json.IsNumber())
+ continue;
+
+ const rapidjson::Value &membership_json = GetMember(content_json, "membership");
+ if(membership_json.IsString() && strcmp(membership_json.GetString(), "invite") == 0) {
+ Invite invite;
+ RoomData invite_room;
+ events_add_user_info(events_json, &invite_room);
+ events_set_room_name(events_json, &invite_room);
+
+ auto invited_by = invite_room.get_user_by_id(sender_json.GetString());
+ if(!invited_by) {
+ fprintf(stderr, "Invited by unknown user. Bug in homeserver?\n");
+ break;
+ }
+
+ invite.room_name = invite_room.get_name();
+ invite.room_avatar_url = invite_room.get_avatar_url();
+ invite.invited_by = invited_by;
+ invite.timestamp = timestamp_json.GetInt64();
+ invite.new_invite = !next_batch.empty();
+
+ std::string room_id_str(room_id.GetString(), room_id.GetStringLength());
+ delegate->add_invite(room_id_str, invite);
+ invites[room_id_str] = std::move(invite);
+ break;
+ }
+ }
+ }
+ }
+
+ void Matrix::remove_rooms(const rapidjson::Value &leave_json, MatrixDelegate *delegate) {
+ if(!leave_json.IsObject())
+ return;
+
+ for(auto const &it : leave_json.GetObject()) {
+ if(!it.value.IsObject())
+ continue;
+
+ const rapidjson::Value &room_id = it.name;
+ if(!room_id.IsString())
+ continue;
+
+ std::string room_id_str(room_id.GetString(), room_id.GetStringLength());
+ auto invites_it = invites.find(room_id_str);
+ if(invites_it != invites.end()) {
+ // TODO: Show leave type and reason and who caused the invite to be removed
+ delegate->remove_invite(room_id_str);
+ invites.erase(invites_it);
+ }
+
+ const rapidjson::Value &timeline_json = GetMember(it.value, "timeline");
+ if(!timeline_json.IsObject())
+ continue;
+
+ const rapidjson::Value &events_json = GetMember(timeline_json, "events");
+ if(!events_json.IsArray())
+ continue;
+
+ 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.room.member") != 0)
+ 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 &membership_json = GetMember(content_json, "membership");
+ if(!membership_json.IsString())
+ continue;
+
+ std::string reason_str;
+ const rapidjson::Value &reason_json = GetMember(content_json, "reason");
+ if(reason_json.IsString())
+ reason_str = reason_json.GetString();
+
+ auto room = get_room_by_id(room_id_str);
+ if(!room)
+ continue;
+
+ std::string desc;
+ LeaveType leave_type;
+ if(strcmp(membership_json.GetString(), "leave") == 0) {
+ if(strcmp(sender_json.GetString(), user_id.c_str()) == 0) {
+ leave_type = LeaveType::LEAVE;
+ } else {
+ leave_type = LeaveType::KICKED;
+ desc = "You were kicked from " + room->get_name() + " by " + sender_json.GetString();
+ }
+ } else if(strcmp(membership_json.GetString(), "ban") == 0) {
+ leave_type = LeaveType::BANNED;
+ desc = "You were banned from " + room->get_name() + " by " + sender_json.GetString();
+ } else {
+ continue;
+ }
+
+ if(!reason_str.empty())
+ desc += ", reason: " + reason_str;
+
+ delegate->leave_room(room, leave_type, desc);
+ remove_room(room_id_str);
+ break;
+ }
+ }
+ }
+
PluginResult Matrix::get_previous_room_messages(RoomData *room_data) {
std::string from = room_data->get_prev_batch();
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;
if(from.empty()) {
fprintf(stderr, "Error: missing next batch!\n");
@@ -2008,12 +2339,10 @@ namespace QuickMedia {
};
std::string server_response;
- if(download_to_string(homeserver + "/_matrix/client/r0/logout", server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK)
- return PluginResult::NET_ERR;
+ download_to_string(homeserver + "/_matrix/client/r0/logout", server_response, std::move(additional_args), use_tor, true);
// Make sure all fields are reset here!
rooms.clear();
- room_list_read_index = 0;
room_data_by_id.clear();
user_id.clear();
username.clear();
@@ -2021,6 +2350,7 @@ namespace QuickMedia {
homeserver.clear();
upload_limit.reset();
next_batch.clear();
+ invites.clear();
return PluginResult::OK;
}
@@ -2119,9 +2449,10 @@ namespace QuickMedia {
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
- if(download_to_string(homeserver + "/_matrix/client/r0/account/whoami", server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK) {
+ 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 PluginResult::NET_ERR;
+ return download_result_to_plugin_result(download_result);
}
this->user_id = std::move(user_id);
@@ -2148,10 +2479,8 @@ namespace QuickMedia {
};
std::string server_response;
- if(download_to_string(homeserver + "/_matrix/client/r0/rooms/" + room->id + "/typing/" + url_param_encode(user_id) , server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK)
- return PluginResult::NET_ERR;
-
- return PluginResult::OK;
+ DownloadResult download_result = download_to_string(homeserver + "/_matrix/client/r0/rooms/" + room->id + "/typing/" + url_param_encode(user_id) , server_response, std::move(additional_args), use_tor, true);
+ return download_result_to_plugin_result(download_result);
}
PluginResult Matrix::on_stop_typing(RoomData *room) {
@@ -2170,10 +2499,8 @@ namespace QuickMedia {
};
std::string server_response;
- if(download_to_string(homeserver + "/_matrix/client/r0/rooms/" + room->id + "/typing/" + url_param_encode(user_id), server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK)
- return PluginResult::NET_ERR;
-
- return PluginResult::OK;
+ DownloadResult download_result = download_to_string(homeserver + "/_matrix/client/r0/rooms/" + room->id + "/typing/" + url_param_encode(user_id), server_response, std::move(additional_args), use_tor, true);
+ return download_result_to_plugin_result(download_result);
}
PluginResult Matrix::set_read_marker(RoomData *room, const Message *message) {
@@ -2194,15 +2521,42 @@ namespace QuickMedia {
};
std::string server_response;
- if(download_to_string(homeserver + "/_matrix/client/r0/rooms/" + room->id + "/read_markers", server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK)
- return PluginResult::NET_ERR;
+ DownloadResult download_result = download_to_string(homeserver + "/_matrix/client/r0/rooms/" + room->id + "/read_markers", server_response, std::move(additional_args), use_tor, true);
+ if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result);
auto me = get_me(room);
if(me)
room->set_user_read_marker(me, message->event_id);
+
return PluginResult::OK;
}
+ PluginResult Matrix::join_room(const std::string &room_id) {
+ std::vector<CommandArg> additional_args = {
+ { "-X", "POST" },
+ { "-H", "content-type: application/json" },
+ { "--data-binary", "{}" },
+ { "-H", "Authorization: Bearer " + access_token }
+ };
+
+ std::string server_response;
+ DownloadResult download_result = download_to_string(homeserver + "/_matrix/client/r0/join/" + room_id, server_response, std::move(additional_args), use_tor, true);
+ return download_result_to_plugin_result(download_result);
+ }
+
+ PluginResult Matrix::leave_room(const std::string &room_id) {
+ std::vector<CommandArg> additional_args = {
+ { "-X", "POST" },
+ { "-H", "content-type: application/json" },
+ { "--data-binary", "{}" },
+ { "-H", "Authorization: Bearer " + access_token }
+ };
+
+ std::string server_response;
+ DownloadResult download_result = download_to_string(homeserver + "/_matrix/client/r0/rooms/" + room_id + "/leave", server_response, std::move(additional_args), use_tor, true);
+ return download_result_to_plugin_result(download_result);
+ }
+
bool Matrix::was_message_posted_by_me(void *message) {
Message *message_typed = (Message*)message;
return user_id == message_typed->user->user_id;
@@ -2264,6 +2618,18 @@ namespace QuickMedia {
rooms.push_back(std::move(room));
}
+ void Matrix::remove_room(const std::string &room_id) {
+ std::lock_guard<std::mutex> lock(room_data_mutex);
+ auto room_it = room_data_by_id.find(room_id);
+ if(room_it == room_data_by_id.end())
+ return;
+
+ // We want to clear data instead of removing the object iself becasue we want to instance to still be valid,
+ // also in the future we can have a "history" tag for rooms we have just left
+ rooms[room_it->second]->clear_data();
+ room_data_by_id.erase(room_it);
+ }
+
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?