// // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/telegram/RecentDialogList.h" #include "td/telegram/AccessRights.h" #include "td/telegram/ChatManager.h" #include "td/telegram/DialogListId.h" #include "td/telegram/DialogManager.h" #include "td/telegram/FolderId.h" #include "td/telegram/Global.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/Td.h" #include "td/telegram/td_api.h" #include "td/telegram/TdDb.h" #include "td/telegram/UserManager.h" #include "td/actor/MultiPromise.h" #include "td/utils/algorithm.h" #include "td/utils/misc.h" #include "td/utils/Slice.h" #include "td/utils/SliceBuilder.h" namespace td { RecentDialogList::RecentDialogList(Td *td, const char *name, size_t max_size) : td_(td), name_(name), max_size_(max_size) { register_actor(PSLICE() << name << "_chats", this).release(); } string RecentDialogList::get_binlog_key() const { return PSTRING() << name_ << "_dialog_usernames_and_ids"; } void RecentDialogList::save_dialogs() const { if (!is_loaded_) { return; } CHECK(removed_dialog_ids_.empty()); SliceBuilder sb; for (auto &dialog_id : dialog_ids_) { sb << ','; if (!G()->use_chat_info_database()) { // if there is no dialog info database, prefer to save dialogs by username string username; switch (dialog_id.get_type()) { case DialogType::User: if (!td_->user_manager_->is_user_contact(dialog_id.get_user_id())) { username = td_->user_manager_->get_user_first_username(dialog_id.get_user_id()); } break; case DialogType::Chat: break; case DialogType::Channel: username = td_->chat_manager_->get_channel_first_username(dialog_id.get_channel_id()); break; case DialogType::SecretChat: break; case DialogType::None: default: UNREACHABLE(); } if (!username.empty() && username.find(',') == string::npos) { sb << '@' << username; continue; } } sb << dialog_id.get(); } auto result = sb.as_cslice(); if (!result.empty()) { result.remove_prefix(1); } G()->td_db()->get_binlog_pmc()->set(get_binlog_key(), result.str()); } void RecentDialogList::load_dialogs(Promise &&promise) { if (is_loaded_) { return promise.set_value(Unit()); } load_list_queries_.push_back(std::move(promise)); if (load_list_queries_.size() != 1) { return; } auto found_dialogs = full_split(G()->td_db()->get_binlog_pmc()->get(get_binlog_key()), ','); MultiPromiseActorSafe mpas{"LoadRecentDialogListMultiPromiseActor"}; mpas.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), found_dialogs](Unit) mutable { send_closure(actor_id, &RecentDialogList::on_load_dialogs, std::move(found_dialogs)); })); mpas.set_ignore_errors(true); auto lock = mpas.get_promise(); vector dialog_ids; for (auto &found_dialog : found_dialogs) { if (found_dialog[0] == '@') { td_->dialog_manager_->search_public_dialog(found_dialog, false, mpas.get_promise()); } else { dialog_ids.push_back(DialogId(to_integer(found_dialog))); } } if (!dialog_ids.empty()) { if (G()->use_chat_info_database() && !G()->td_db()->was_dialog_db_created()) { td_->messages_manager_->load_dialogs( std::move(dialog_ids), PromiseCreator::lambda( [promise = mpas.get_promise()](vector dialog_ids) mutable { promise.set_value(Unit()); })); } else { td_->messages_manager_->get_dialogs_from_list( DialogListId(FolderId::main()), 102, PromiseCreator::lambda([promise = mpas.get_promise()](td_api::object_ptr &&chats) mutable { promise.set_value(Unit()); })); td_->user_manager_->search_contacts("", 1, mpas.get_promise()); } } lock.set_value(Unit()); } void RecentDialogList::on_load_dialogs(vector &&found_dialogs) { auto promises = std::move(load_list_queries_); CHECK(!promises.empty()); if (G()->close_flag()) { return fail_promises(promises, Global::request_aborted_error()); } auto newly_found_dialogs = std::move(dialog_ids_); reset_to_empty(dialog_ids_); for (auto it = found_dialogs.rbegin(); it != found_dialogs.rend(); ++it) { DialogId dialog_id; if ((*it)[0] == '@') { dialog_id = td_->dialog_manager_->get_resolved_dialog_by_username(it->substr(1)); } else { dialog_id = DialogId(to_integer(*it)); } if (dialog_id.is_valid() && td::contains(removed_dialog_ids_, dialog_id) == 0 && td_->dialog_manager_->have_dialog_info(dialog_id) && td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { td_->dialog_manager_->force_create_dialog(dialog_id, "recent dialog"); do_add_dialog(dialog_id); } } for (auto it = newly_found_dialogs.rbegin(); it != newly_found_dialogs.rend(); ++it) { do_add_dialog(*it); } is_loaded_ = true; removed_dialog_ids_.clear(); if (!newly_found_dialogs.empty()) { save_dialogs(); } set_promises(promises); } void RecentDialogList::add_dialog(DialogId dialog_id) { if (!is_loaded_) { load_dialogs(Promise()); } if (do_add_dialog(dialog_id)) { save_dialogs(); } } bool RecentDialogList::do_add_dialog(DialogId dialog_id) { if (!dialog_ids_.empty() && dialog_ids_[0] == dialog_id) { return false; } add_to_top(dialog_ids_, max_size_, dialog_id); td::remove(removed_dialog_ids_, dialog_id); return true; } void RecentDialogList::remove_dialog(DialogId dialog_id) { if (!dialog_id.is_valid()) { return; } if (!is_loaded_) { load_dialogs(Promise()); } if (td::remove(dialog_ids_, dialog_id)) { save_dialogs(); } else if (!is_loaded_ && !td::contains(removed_dialog_ids_, dialog_id)) { removed_dialog_ids_.push_back(dialog_id); } } void RecentDialogList::update_dialogs() { CHECK(is_loaded_); vector dialog_ids; for (auto dialog_id : dialog_ids_) { if (!td_->messages_manager_->have_dialog(dialog_id)) { continue; } switch (dialog_id.get_type()) { case DialogType::User: // always keep break; case DialogType::Chat: { auto channel_id = td_->chat_manager_->get_chat_migrated_to_channel_id(dialog_id.get_chat_id()); if (channel_id.is_valid() && td_->messages_manager_->have_dialog(DialogId(channel_id))) { dialog_id = DialogId(channel_id); } break; } case DialogType::Channel: // always keep break; case DialogType::SecretChat: if (td_->messages_manager_->is_deleted_secret_chat(dialog_id)) { dialog_id = DialogId(); } break; case DialogType::None: default: UNREACHABLE(); break; } if (dialog_id.is_valid()) { dialog_ids.push_back(dialog_id); } } if (dialog_ids != dialog_ids_) { dialog_ids_ = std::move(dialog_ids); save_dialogs(); } } std::pair> RecentDialogList::get_dialogs(int32 limit, Promise &&promise) { load_dialogs(std::move(promise)); if (!is_loaded_) { return {}; } update_dialogs(); CHECK(limit >= 0); auto total_count = narrow_cast(dialog_ids_.size()); return {total_count, vector(dialog_ids_.begin(), dialog_ids_.begin() + min(limit, total_count))}; } void RecentDialogList::clear_dialogs() { if (dialog_ids_.empty() && is_loaded_) { return; } is_loaded_ = true; dialog_ids_.clear(); removed_dialog_ids_.clear(); save_dialogs(); } } // namespace td