diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/QuickMedia.cpp | 68 | ||||
-rw-r--r-- | src/plugins/Matrix.cpp | 598 |
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, ¤t_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 ×tamp_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? |