diff --git a/CMakeLists.txt b/CMakeLists.txt index 317fc4660..105d91d00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -388,6 +388,7 @@ set(TDLIB_SOURCE td/telegram/PhotoSizeSource.cpp td/telegram/PollManager.cpp td/telegram/QueryCombiner.cpp + td/telegram/RecentDialogList.cpp td/telegram/ReplyMarkup.cpp td/telegram/ReportReason.cpp td/telegram/RestrictionReason.cpp @@ -596,6 +597,7 @@ set(TDLIB_SOURCE td/telegram/PtsManager.h td/telegram/PublicDialogType.h td/telegram/QueryCombiner.h + td/telegram/RecentDialogList.h td/telegram/ReplyMarkup.h td/telegram/ReportReason.h td/telegram/RequestActor.h diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index 54ea5dcbc..50782456d 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -5694,7 +5694,8 @@ MessagesManager::Dialog::~Dialog() { } } -MessagesManager::MessagesManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { +MessagesManager::MessagesManager(Td *td, ActorShared<> parent) + : recently_found_dialogs_{td, "recently_found", MAX_RECENTLY_FOUND_DIALOGS}, td_(td), parent_(std::move(parent)) { upload_media_callback_ = std::make_shared(); upload_thumbnail_callback_ = std::make_shared(); upload_dialog_photo_callback_ = std::make_shared(); @@ -10917,9 +10918,7 @@ void MessagesManager::on_dialog_deleted(DialogId dialog_id, Promise &&prom d->is_empty = false; d->need_restore_reply_markup = true; } - if (remove_recently_found_dialog_internal(dialog_id)) { - save_recently_found_dialogs(); - } + recently_found_dialogs_.remove_dialog(dialog_id); if (dialog_id.get_type() == DialogType::Channel) { G()->td_db()->get_binlog_pmc()->erase(get_channel_pts_key(dialog_id)); } @@ -16249,17 +16248,13 @@ std::pair> MessagesManager::search_dialogs(const string return {}; } if (query.empty()) { - if (!load_recently_found_dialogs(promise)) { + if (!recently_found_dialogs_.load_dialogs(promise)) { return {}; } promise.set_value(Unit()); - update_recently_found_dialogs(); - - size_t result_size = min(static_cast(limit), recently_found_dialog_ids_.size()); - return {narrow_cast(recently_found_dialog_ids_.size()), - vector(recently_found_dialog_ids_.begin(), recently_found_dialog_ids_.begin() + result_size)}; + return recently_found_dialogs_.get_dialogs(limit); } auto result = dialogs_hints_.search(query, limit); @@ -24743,6 +24738,10 @@ bool MessagesManager::is_broadcast_channel(DialogId dialog_id) const { ContactsManager::ChannelType::Broadcast; } +bool MessagesManager::is_deleted_secret_chat(DialogId dialog_id) const { + return is_deleted_secret_chat(get_dialog(dialog_id)); +} + bool MessagesManager::is_deleted_secret_chat(const Dialog *d) const { if (d == nullptr) { return true; @@ -37306,213 +37305,24 @@ void MessagesManager::on_binlog_events(vector &&events) { } } -void MessagesManager::save_recently_found_dialogs() { - if (recently_found_dialogs_loaded_ < 2) { - return; - } - - string value; - for (auto &dialog_id : recently_found_dialog_ids_) { - if (!value.empty()) { - value += ','; - } - if (!G()->parameters().use_message_db) { - // if there is no dialog database, prefer to save dialogs by username - auto username = get_dialog_username(dialog_id); - if (dialog_id.get_type() != DialogType::SecretChat && !username.empty()) { - value += '@'; - value += username; - continue; - } - } - value += to_string(dialog_id.get()); - } - LOG(DEBUG) << "Save recently found chats " << value; - G()->td_db()->get_binlog_pmc()->set("recently_found_dialog_usernames_and_ids", value); -} - -bool MessagesManager::load_recently_found_dialogs(Promise &promise) { - CHECK(!td_->auth_manager_->is_bot()); - if (recently_found_dialogs_loaded_ >= 2) { - return true; - } - - string found_dialogs_str = G()->td_db()->get_binlog_pmc()->get("recently_found_dialog_usernames_and_ids"); - if (found_dialogs_str.empty()) { - recently_found_dialogs_loaded_ = 2; - if (!recently_found_dialog_ids_.empty()) { - save_recently_found_dialogs(); - } - return true; - } - - LOG(DEBUG) << "Loaded recently found chats " << found_dialogs_str; - auto found_dialogs = full_split(found_dialogs_str, ','); - if (recently_found_dialogs_loaded_ == 1 && resolve_recently_found_dialogs_multipromise_.promise_count() == 0) { - // queries was sent and have already been finished - auto newly_found_dialogs = std::move(recently_found_dialog_ids_); - recently_found_dialog_ids_.clear(); - - for (auto it = found_dialogs.rbegin(); it != found_dialogs.rend(); ++it) { - if ((*it)[0] == '@') { - auto dialog_id = resolve_dialog_username(it->substr(1)); - if (dialog_id.is_valid() && have_input_peer(dialog_id, AccessRights::Read)) { - force_create_dialog(dialog_id, "recently found resolved dialog"); - add_recently_found_dialog_internal(dialog_id); - } - } else { - auto dialog_id = DialogId(to_integer(*it)); - CHECK(dialog_id.is_valid()); - if (have_input_peer(dialog_id, AccessRights::Read)) { - force_create_dialog(dialog_id, "recently found dialog"); - add_recently_found_dialog_internal(dialog_id); - } - } - } - for (auto it = newly_found_dialogs.rbegin(); it != newly_found_dialogs.rend(); ++it) { - add_recently_found_dialog_internal(*it); - } - recently_found_dialogs_loaded_ = 2; - if (!newly_found_dialogs.empty()) { - save_recently_found_dialogs(); - } - return true; - } - - resolve_recently_found_dialogs_multipromise_.add_promise(std::move(promise)); - if (recently_found_dialogs_loaded_ == 0) { - recently_found_dialogs_loaded_ = 1; - - resolve_recently_found_dialogs_multipromise_.set_ignore_errors(true); - auto lock = resolve_recently_found_dialogs_multipromise_.get_promise(); - - for (auto &found_dialog : found_dialogs) { - if (found_dialog[0] == '@') { - search_public_dialog(found_dialog, false, resolve_recently_found_dialogs_multipromise_.get_promise()); - } - } - if (G()->parameters().use_message_db) { - for (auto &found_dialog : found_dialogs) { - if (found_dialog[0] != '@') { - auto dialog_id = DialogId(to_integer(found_dialog)); - CHECK(dialog_id.is_valid()); - // TODO use asynchronous load - // get_dialog(dialog_id, resolve_recently_found_dialogs_multipromise_.get_promise()); - get_dialog_force(dialog_id, "load_recently_found_dialogs"); - } - } - } else { - get_dialogs_from_list(DialogListId(FolderId::main()), MAX_GET_DIALOGS + 2, - PromiseCreator::lambda( - [promise = resolve_recently_found_dialogs_multipromise_.get_promise()]( - td_api::object_ptr &&chats) mutable { promise.set_value(Unit()); })); - td_->contacts_manager_->search_contacts("", 1, resolve_recently_found_dialogs_multipromise_.get_promise()); - } - - lock.set_value(Unit()); - } - return false; -} - Status MessagesManager::add_recently_found_dialog(DialogId dialog_id) { if (!have_dialog_force(dialog_id, "add_recently_found_dialog")) { - return Status::Error(5, "Chat not found"); + return Status::Error(400, "Chat not found"); } - if (add_recently_found_dialog_internal(dialog_id)) { - save_recently_found_dialogs(); - } - + recently_found_dialogs_.add_dialog(dialog_id); return Status::OK(); } Status MessagesManager::remove_recently_found_dialog(DialogId dialog_id) { if (!have_dialog_force(dialog_id, "remove_recently_found_dialog")) { - return Status::Error(5, "Chat not found"); + return Status::Error(400, "Chat not found"); } - if (remove_recently_found_dialog_internal(dialog_id)) { - save_recently_found_dialogs(); - } - + recently_found_dialogs_.remove_dialog(dialog_id); return Status::OK(); } void MessagesManager::clear_recently_found_dialogs() { - recently_found_dialogs_loaded_ = 2; - if (recently_found_dialog_ids_.empty()) { - return; - } - - recently_found_dialog_ids_.clear(); - save_recently_found_dialogs(); -} - -bool MessagesManager::add_recently_found_dialog_internal(DialogId dialog_id) { - CHECK(have_dialog(dialog_id)); - - if (!recently_found_dialog_ids_.empty() && recently_found_dialog_ids_[0] == dialog_id) { - return false; - } - - // TODO create function - auto it = std::find(recently_found_dialog_ids_.begin(), recently_found_dialog_ids_.end(), dialog_id); - if (it == recently_found_dialog_ids_.end()) { - if (narrow_cast(recently_found_dialog_ids_.size()) == MAX_RECENTLY_FOUND_DIALOGS) { - CHECK(!recently_found_dialog_ids_.empty()); - recently_found_dialog_ids_.back() = dialog_id; - } else { - recently_found_dialog_ids_.push_back(dialog_id); - } - it = recently_found_dialog_ids_.end() - 1; - } - std::rotate(recently_found_dialog_ids_.begin(), it, it + 1); - return true; -} - -bool MessagesManager::remove_recently_found_dialog_internal(DialogId dialog_id) { - CHECK(have_dialog(dialog_id)); - return td::remove(recently_found_dialog_ids_, dialog_id); -} - -void MessagesManager::update_recently_found_dialogs() { - vector dialog_ids; - for (auto dialog_id : recently_found_dialog_ids_) { - const Dialog *d = get_dialog(dialog_id); - if (d == nullptr) { - continue; - } - switch (dialog_id.get_type()) { - case DialogType::User: - // always keep - break; - case DialogType::Chat: { - auto channel_id = td_->contacts_manager_->get_chat_migrated_to_channel_id(dialog_id.get_chat_id()); - if (channel_id.is_valid() && get_dialog(DialogId(channel_id)) != nullptr) { - dialog_id = DialogId(channel_id); - } - break; - } - case DialogType::Channel: - // always keep - break; - case DialogType::SecretChat: - if (is_deleted_secret_chat(d)) { - dialog_id = DialogId(); - } - break; - case DialogType::None: - default: - UNREACHABLE(); - break; - } - if (dialog_id.is_valid()) { - dialog_ids.push_back(dialog_id); - } - } - - if (dialog_ids != recently_found_dialog_ids_) { - recently_found_dialog_ids_ = std::move(dialog_ids); - save_recently_found_dialogs(); - } + recently_found_dialogs_.clear_dialogs(); } void MessagesManager::suffix_load_loop(Dialog *d) { diff --git a/td/telegram/MessagesManager.h b/td/telegram/MessagesManager.h index c57a34f98..58624c944 100644 --- a/td/telegram/MessagesManager.h +++ b/td/telegram/MessagesManager.h @@ -42,6 +42,7 @@ #include "td/telegram/NotificationGroupType.h" #include "td/telegram/NotificationId.h" #include "td/telegram/NotificationSettings.h" +#include "td/telegram/RecentDialogList.h" #include "td/telegram/ReplyMarkup.h" #include "td/telegram/ReportReason.h" #include "td/telegram/RestrictionReason.h" @@ -575,6 +576,8 @@ class MessagesManager final : public Actor { bool is_message_edited_recently(FullMessageId full_message_id, int32 seconds); + bool is_deleted_secret_chat(DialogId dialog_id) const; + Result> get_message_link(FullMessageId full_message_id, int32 media_timestamp, bool for_group, bool for_comment); @@ -2959,16 +2962,6 @@ class MessagesManager final : public Actor { static MessageId get_next_yet_unsent_scheduled_message_id(Dialog *d, int32 date); - bool add_recently_found_dialog_internal(DialogId dialog_id); - - bool remove_recently_found_dialog_internal(DialogId dialog_id); - - void update_recently_found_dialogs(); - - void save_recently_found_dialogs(); - - bool load_recently_found_dialogs(Promise &promise); - void reget_message_from_server_if_needed(DialogId dialog_id, const Message *m); void speculatively_update_active_group_call_id(Dialog *d, const Message *m); @@ -3063,10 +3056,7 @@ class MessagesManager final : public Actor { static DialogId get_message_original_sender(const Message *m); - int32 recently_found_dialogs_loaded_ = 0; // 0 - not loaded, 1 - load request was sent, 2 - loaded - MultiPromiseActor resolve_recently_found_dialogs_multipromise_{"ResolveRecentlyFoundDialogsMultiPromiseActor"}; - - vector recently_found_dialog_ids_; + RecentDialogList recently_found_dialogs_; class UploadMediaCallback; class UploadThumbnailCallback; diff --git a/td/telegram/RecentDialogList.cpp b/td/telegram/RecentDialogList.cpp new file mode 100644 index 000000000..359963c81 --- /dev/null +++ b/td/telegram/RecentDialogList.cpp @@ -0,0 +1,243 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021 +// +// 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/ContactsManager.h" +#include "td/telegram/Global.h" +#include "td/telegram/MessagesManager.h" +#include "td/telegram/Td.h" +#include "td/telegram/TdDb.h" +#include "td/telegram/TdParameters.h" + +#include "td/utils/algorithm.h" +#include "td/utils/misc.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) { +} + +string RecentDialogList::get_binlog_key() const { + return PSTRING() << name_ << "_dialog_usernames_and_ids"; +} + +void RecentDialogList::save_dialogs() { + if (dialogs_loaded_ < 2) { + return; + } + + string value; + for (auto &dialog_id : dialog_ids_) { + if (!value.empty()) { + value += ','; + } + if (!G()->parameters().use_message_db) { + // if there is no dialog database, prefer to save dialogs by username + string username; + switch (dialog_id.get_type()) { + case DialogType::User: + username = td_->contacts_manager_->get_user_username(dialog_id.get_user_id()); + break; + case DialogType::Chat: + break; + case DialogType::Channel: + username = td_->contacts_manager_->get_channel_username(dialog_id.get_channel_id()); + break; + case DialogType::SecretChat: + break; + case DialogType::None: + default: + UNREACHABLE(); + } + if (!username.empty()) { + value += '@'; + value += username; + continue; + } + } + value += to_string(dialog_id.get()); + } + G()->td_db()->get_binlog_pmc()->set(get_binlog_key(), value); +} + +bool RecentDialogList::load_dialogs(Promise &promise) { + if (dialogs_loaded_ >= 2) { + return true; + } + + string found_dialogs_str = G()->td_db()->get_binlog_pmc()->get(get_binlog_key()); + if (found_dialogs_str.empty()) { + dialogs_loaded_ = 2; + if (!dialog_ids_.empty()) { + save_dialogs(); + } + return true; + } + + auto found_dialogs = full_split(found_dialogs_str, ','); + if (dialogs_loaded_ == 1 && resolve_dialogs_multipromise_.promise_count() == 0) { + // queries was sent and have already been finished + auto newly_found_dialogs = std::move(dialog_ids_); + dialog_ids_.clear(); + + for (auto it = found_dialogs.rbegin(); it != found_dialogs.rend(); ++it) { + if ((*it)[0] == '@') { + auto dialog_id = td_->messages_manager_->resolve_dialog_username(it->substr(1)); + if (dialog_id.is_valid() && td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + td_->messages_manager_->force_create_dialog(dialog_id, "resolved recent dialog"); + do_add_dialog(dialog_id); + } + } else { + auto dialog_id = DialogId(to_integer(*it)); + CHECK(dialog_id.is_valid()); + if (td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + td_->messages_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); + } + dialogs_loaded_ = 2; + if (!newly_found_dialogs.empty()) { + save_dialogs(); + } + return true; + } + + resolve_dialogs_multipromise_.add_promise(std::move(promise)); + if (dialogs_loaded_ == 0) { + dialogs_loaded_ = 1; + + resolve_dialogs_multipromise_.set_ignore_errors(true); + auto lock = resolve_dialogs_multipromise_.get_promise(); + + for (auto &found_dialog : found_dialogs) { + if (found_dialog[0] == '@') { + td_->messages_manager_->search_public_dialog(found_dialog, false, resolve_dialogs_multipromise_.get_promise()); + } + } + if (G()->parameters().use_message_db) { + for (auto &found_dialog : found_dialogs) { + if (found_dialog[0] != '@') { + auto dialog_id = DialogId(to_integer(found_dialog)); + CHECK(dialog_id.is_valid()); + // TODO use asynchronous load + // get_dialog(dialog_id, resolve_dialogs_multipromise_.get_promise()); + td_->messages_manager_->have_dialog_force(dialog_id, "RecentDialogList::load_dialogs"); + } + } + } else { + td_->messages_manager_->get_dialogs_from_list( + DialogListId(FolderId::main()), 102, + PromiseCreator::lambda( + [promise = resolve_dialogs_multipromise_.get_promise()]( + td_api::object_ptr &&chats) mutable { promise.set_value(Unit()); })); + td_->contacts_manager_->search_contacts("", 1, resolve_dialogs_multipromise_.get_promise()); + } + + lock.set_value(Unit()); + } + return false; +} + +void RecentDialogList::add_dialog(DialogId dialog_id) { + 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; + } + + // TODO create function + auto it = std::find(dialog_ids_.begin(), dialog_ids_.end(), dialog_id); + if (it == dialog_ids_.end()) { + if (dialog_ids_.size() == max_size_) { + CHECK(!dialog_ids_.empty()); + dialog_ids_.back() = dialog_id; + } else { + dialog_ids_.push_back(dialog_id); + } + it = dialog_ids_.end() - 1; + } + std::rotate(dialog_ids_.begin(), it, it + 1); + return true; +} + +void RecentDialogList::remove_dialog(DialogId dialog_id) { + if (td::remove(dialog_ids_, dialog_id)) { + save_dialogs(); + } +} + +void RecentDialogList::update_dialogs() { + 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_->contacts_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) { + CHECK(dialogs_loaded_ == 2); + update_dialogs(); + + size_t result_size = min(static_cast(limit), dialog_ids_.size()); + return {narrow_cast(dialog_ids_.size()), + vector(dialog_ids_.begin(), dialog_ids_.begin() + result_size)}; +} + +void RecentDialogList::clear_dialogs() { + if (dialog_ids_.empty() && dialogs_loaded_ == 2) { + return; + } + + dialogs_loaded_ = 2; + dialog_ids_.clear(); + save_dialogs(); +} + +} // namespace td diff --git a/td/telegram/RecentDialogList.h b/td/telegram/RecentDialogList.h new file mode 100644 index 000000000..7a355c9e5 --- /dev/null +++ b/td/telegram/RecentDialogList.h @@ -0,0 +1,54 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021 +// +// 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) +// +#pragma once + +#include "td/telegram/DialogId.h" + +#include "td/actor/MultiPromise.h" + +#include "td/utils/common.h" + +#include + +namespace td { + +class Td; + +// stores list of Dialog identifiers of a limited size +class RecentDialogList : public Actor { + public: + RecentDialogList(Td *td, const char *name, size_t max_size); + + void add_dialog(DialogId dialog_id); + + void remove_dialog(DialogId dialog_id); + + bool load_dialogs(Promise &promise); + + std::pair> get_dialogs(int32 limit); + + void clear_dialogs(); + + private: + Td *td_; + const char *name_; + size_t max_size_; + vector dialog_ids_; + + int32 dialogs_loaded_ = 0; // 0 - not loaded, 1 - load request was sent, 2 - loaded + MultiPromiseActor resolve_dialogs_multipromise_{"ResolveDialogsMultiPromiseActor"}; + + bool do_add_dialog(DialogId dialog_id); + + string get_binlog_key() const; + + void update_dialogs(); + + void save_dialogs(); +}; + +} // namespace td