diff --git a/td/telegram/ChannelRecommendationManager.cpp b/td/telegram/ChannelRecommendationManager.cpp index dc507d61b..3d42ac4e3 100644 --- a/td/telegram/ChannelRecommendationManager.cpp +++ b/td/telegram/ChannelRecommendationManager.cpp @@ -6,14 +6,391 @@ // #include "td/telegram/ChannelRecommendationManager.h" +#include "td/telegram/Application.h" +#include "td/telegram/ContactsManager.h" +#include "td/telegram/Dependencies.h" +#include "td/telegram/DialogManager.h" +#include "td/telegram/Global.h" +#include "td/telegram/logevent/LogEvent.h" +#include "td/telegram/logevent/LogEventHelper.h" +#include "td/telegram/OptionManager.h" +#include "td/telegram/Td.h" +#include "td/telegram/TdDb.h" + +#include "td/db/SqliteKeyValueAsync.h" + +#include "td/utils/buffer.h" +#include "td/utils/logging.h" +#include "td/utils/SliceBuilder.h" +#include "td/utils/Time.h" +#include "td/utils/tl_helpers.h" + namespace td { +class GetChannelRecommendationsQuery final : public Td::ResultHandler { + Promise>>> promise_; + ChannelId channel_id_; + + public: + explicit GetChannelRecommendationsQuery( + Promise>>> &&promise) + : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id) { + channel_id_ = channel_id; + + auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + send_query( + G()->net_query_creator().create(telegram_api::channels_getChannelRecommendations(std::move(input_channel)))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto chats_ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for GetChannelRecommendationsQuery: " << to_string(chats_ptr); + switch (chats_ptr->get_id()) { + case telegram_api::messages_chats::ID: { + auto chats = move_tl_object_as(chats_ptr); + auto total_count = static_cast(chats->chats_.size()); + return promise_.set_value({total_count, std::move(chats->chats_)}); + } + case telegram_api::messages_chatsSlice::ID: { + auto chats = move_tl_object_as(chats_ptr); + return promise_.set_value({chats->count_, std::move(chats->chats_)}); + } + default: + UNREACHABLE(); + return promise_.set_error(Status::Error("Unreachable")); + } + } + + void on_error(Status status) final { + td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelRecommendationsQuery"); + promise_.set_error(std::move(status)); + } +}; + +template +void ChannelRecommendationManager::RecommendedDialogs::store(StorerT &storer) const { + bool has_dialog_ids = !dialog_ids_.empty(); + bool has_total_count = static_cast(total_count_) != dialog_ids_.size(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_dialog_ids); + STORE_FLAG(has_total_count); + END_STORE_FLAGS(); + if (has_dialog_ids) { + td::store(dialog_ids_, storer); + } + store_time(next_reload_time_, storer); + if (has_total_count) { + td::store(total_count_, storer); + } +} + +template +void ChannelRecommendationManager::RecommendedDialogs::parse(ParserT &parser) { + bool has_dialog_ids; + bool has_total_count; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_dialog_ids); + PARSE_FLAG(has_total_count); + END_PARSE_FLAGS(); + if (has_dialog_ids) { + td::parse(dialog_ids_, parser); + } + parse_time(next_reload_time_, parser); + if (has_total_count) { + td::parse(total_count_, parser); + } else { + total_count_ = static_cast(dialog_ids_.size()); + } +} + ChannelRecommendationManager::ChannelRecommendationManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { + if (G()->use_sqlite_pmc() && !G()->use_message_database()) { + G()->td_db()->get_sqlite_pmc()->erase_by_prefix("channel_recommendations", Auto()); + } } void ChannelRecommendationManager::tear_down() { parent_.reset(); } +bool ChannelRecommendationManager::is_suitable_recommended_channel(DialogId dialog_id) const { + if (dialog_id.get_type() != DialogType::Channel) { + return false; + } + return is_suitable_recommended_channel(dialog_id.get_channel_id()); +} + +bool ChannelRecommendationManager::is_suitable_recommended_channel(ChannelId channel_id) const { + auto status = td_->contacts_manager_->get_channel_status(channel_id); + return !status.is_member() && td_->contacts_manager_->have_input_peer_channel(channel_id, AccessRights::Read); +} + +bool ChannelRecommendationManager::are_suitable_recommended_dialogs( + const RecommendedDialogs &recommended_dialogs) const { + for (auto recommended_dialog_id : recommended_dialogs.dialog_ids_) { + if (!is_suitable_recommended_channel(recommended_dialog_id)) { + return false; + } + } + auto is_premium = td_->option_manager_->get_option_boolean("is_premium"); + auto have_all = recommended_dialogs.dialog_ids_.size() == static_cast(recommended_dialogs.total_count_); + if (!have_all && is_premium) { + return false; + } + return true; +} + +void ChannelRecommendationManager::get_channel_recommendations( + DialogId dialog_id, bool return_local, Promise> &&chats_promise, + Promise> &&count_promise) { + if (!td_->dialog_manager_->have_dialog_force(dialog_id, "get_channel_recommendations")) { + if (chats_promise) { + chats_promise.set_error(Status::Error(400, "Chat not found")); + } + if (count_promise) { + count_promise.set_error(Status::Error(400, "Chat not found")); + } + return; + } + if (dialog_id.get_type() != DialogType::Channel) { + if (chats_promise) { + chats_promise.set_value(td_api::make_object()); + } + if (count_promise) { + count_promise.set_value(td_api::make_object(0)); + } + return; + } + auto channel_id = dialog_id.get_channel_id(); + if (!td_->contacts_manager_->is_broadcast_channel(channel_id) || + td_->contacts_manager_->get_input_channel(channel_id) == nullptr) { + if (chats_promise) { + chats_promise.set_value(td_api::make_object()); + } + if (count_promise) { + count_promise.set_value(td_api::make_object(0)); + } + return; + } + bool use_database = true; + auto it = channel_recommended_dialogs_.find(channel_id); + if (it != channel_recommended_dialogs_.end()) { + if (are_suitable_recommended_dialogs(it->second)) { + auto next_reload_time = it->second.next_reload_time_; + if (chats_promise) { + chats_promise.set_value(td_->dialog_manager_->get_chats_object(it->second.total_count_, it->second.dialog_ids_, + "get_channel_recommendations")); + } + if (count_promise) { + count_promise.set_value(td_api::make_object(it->second.total_count_)); + } + if (next_reload_time > Time::now()) { + return; + } + chats_promise = {}; + count_promise = {}; + } else { + LOG(INFO) << "Drop cache for similar chats of " << dialog_id; + channel_recommended_dialogs_.erase(it); + if (G()->use_message_database()) { + G()->td_db()->get_sqlite_pmc()->erase(get_channel_recommendations_database_key(channel_id), Auto()); + } + } + use_database = false; + } + load_channel_recommendations(channel_id, use_database, return_local, std::move(chats_promise), + std::move(count_promise)); +} + +string ChannelRecommendationManager::get_channel_recommendations_database_key(ChannelId channel_id) { + return PSTRING() << "channel_recommendations" << channel_id.get(); +} + +void ChannelRecommendationManager::load_channel_recommendations( + ChannelId channel_id, bool use_database, bool return_local, + Promise> &&chats_promise, + Promise> &&count_promise) { + if (count_promise) { + get_channel_recommendation_count_queries_[return_local][channel_id].push_back(std::move(count_promise)); + } + auto &queries = get_channel_recommendations_queries_[channel_id]; + queries.push_back(std::move(chats_promise)); + if (queries.size() == 1) { + if (G()->use_message_database() && use_database) { + G()->td_db()->get_sqlite_pmc()->get( + get_channel_recommendations_database_key(channel_id), + PromiseCreator::lambda([actor_id = actor_id(this), channel_id](string value) { + send_closure(actor_id, &ChannelRecommendationManager::on_load_channel_recommendations_from_database, + channel_id, std::move(value)); + })); + } else { + reload_channel_recommendations(channel_id); + } + } +} + +void ChannelRecommendationManager::fail_load_channel_recommendations_queries(ChannelId channel_id, Status &&error) { + for (int return_local = 0; return_local < 2; return_local++) { + auto it = get_channel_recommendation_count_queries_[return_local].find(channel_id); + if (it != get_channel_recommendation_count_queries_[return_local].end()) { + auto promises = std::move(it->second); + CHECK(!promises.empty()); + get_channel_recommendation_count_queries_[return_local].erase(it); + fail_promises(promises, error.clone()); + } + } + auto it = get_channel_recommendations_queries_.find(channel_id); + CHECK(it != get_channel_recommendations_queries_.end()); + auto promises = std::move(it->second); + CHECK(!promises.empty()); + get_channel_recommendations_queries_.erase(it); + fail_promises(promises, std::move(error)); +} + +void ChannelRecommendationManager::finish_load_channel_recommendations_queries(ChannelId channel_id, int32 total_count, + vector dialog_ids) { + for (int return_local = 0; return_local < 2; return_local++) { + auto it = get_channel_recommendation_count_queries_[return_local].find(channel_id); + if (it != get_channel_recommendation_count_queries_[return_local].end()) { + auto promises = std::move(it->second); + CHECK(!promises.empty()); + get_channel_recommendation_count_queries_[return_local].erase(it); + for (auto &promise : promises) { + promise.set_value(td_api::make_object(total_count)); + } + } + } + auto it = get_channel_recommendations_queries_.find(channel_id); + CHECK(it != get_channel_recommendations_queries_.end()); + auto promises = std::move(it->second); + CHECK(!promises.empty()); + get_channel_recommendations_queries_.erase(it); + for (auto &promise : promises) { + if (promise) { + promise.set_value(td_->dialog_manager_->get_chats_object(total_count, dialog_ids, + "finish_load_channel_recommendations_queries")); + } + } +} + +void ChannelRecommendationManager::on_load_channel_recommendations_from_database(ChannelId channel_id, string value) { + if (G()->close_flag()) { + return fail_load_channel_recommendations_queries(channel_id, G()->close_status()); + } + + if (value.empty()) { + return reload_channel_recommendations(channel_id); + } + auto &recommended_dialogs = channel_recommended_dialogs_[channel_id]; + if (log_event_parse(recommended_dialogs, value).is_error()) { + channel_recommended_dialogs_.erase(channel_id); + G()->td_db()->get_sqlite_pmc()->erase(get_channel_recommendations_database_key(channel_id), Auto()); + return reload_channel_recommendations(channel_id); + } + Dependencies dependencies; + for (auto dialog_id : recommended_dialogs.dialog_ids_) { + dependencies.add_dialog_and_dependencies(dialog_id); + } + if (!dependencies.resolve_force(td_, "on_load_channel_recommendations_from_database") || + !are_suitable_recommended_dialogs(recommended_dialogs)) { + channel_recommended_dialogs_.erase(channel_id); + G()->td_db()->get_sqlite_pmc()->erase(get_channel_recommendations_database_key(channel_id), Auto()); + return reload_channel_recommendations(channel_id); + } + + auto next_reload_time = recommended_dialogs.next_reload_time_; + finish_load_channel_recommendations_queries(channel_id, recommended_dialogs.total_count_, + recommended_dialogs.dialog_ids_); + + if (next_reload_time <= Time::now()) { + load_channel_recommendations(channel_id, false, false, Auto(), Auto()); + } +} + +void ChannelRecommendationManager::reload_channel_recommendations(ChannelId channel_id) { + auto it = get_channel_recommendation_count_queries_[1].find(channel_id); + if (it != get_channel_recommendation_count_queries_[1].end()) { + auto promises = std::move(it->second); + CHECK(!promises.empty()); + get_channel_recommendation_count_queries_[1].erase(it); + for (auto &promise : promises) { + promise.set_value(td_api::make_object(-1)); + } + } + auto query_promise = + PromiseCreator::lambda([actor_id = actor_id(this), channel_id]( + Result>>> &&result) { + send_closure(actor_id, &ChannelRecommendationManager::on_get_channel_recommendations, channel_id, + std::move(result)); + }); + td_->create_handler(std::move(query_promise))->send(channel_id); +} + +void ChannelRecommendationManager::on_get_channel_recommendations( + ChannelId channel_id, Result>>> &&r_chats) { + G()->ignore_result_if_closing(r_chats); + + if (r_chats.is_error()) { + return fail_load_channel_recommendations_queries(channel_id, r_chats.move_as_error()); + } + + auto chats = r_chats.move_as_ok(); + auto total_count = chats.first; + auto channel_ids = td_->contacts_manager_->get_channel_ids(std::move(chats.second), "on_get_channel_recommendations"); + vector dialog_ids; + if (total_count < static_cast(channel_ids.size())) { + LOG(ERROR) << "Receive total_count = " << total_count << " and " << channel_ids.size() << " similar chats for " + << channel_id; + total_count = static_cast(channel_ids.size()); + } + for (auto recommended_channel_id : channel_ids) { + auto recommended_dialog_id = DialogId(recommended_channel_id); + td_->dialog_manager_->force_create_dialog(recommended_dialog_id, "on_get_channel_recommendations"); + if (is_suitable_recommended_channel(recommended_channel_id)) { + dialog_ids.push_back(recommended_dialog_id); + } else { + total_count--; + } + } + auto &recommended_dialogs = channel_recommended_dialogs_[channel_id]; + recommended_dialogs.total_count_ = total_count; + recommended_dialogs.dialog_ids_ = dialog_ids; + recommended_dialogs.next_reload_time_ = Time::now() + CHANNEL_RECOMMENDATIONS_CACHE_TIME; + + if (G()->use_message_database()) { + G()->td_db()->get_sqlite_pmc()->set(get_channel_recommendations_database_key(channel_id), + log_event_store(recommended_dialogs).as_slice().str(), Promise()); + } + + finish_load_channel_recommendations_queries(channel_id, total_count, std::move(dialog_ids)); +} + +void ChannelRecommendationManager::open_channel_recommended_channel(DialogId dialog_id, DialogId opened_dialog_id, + Promise &&promise) { + if (!td_->dialog_manager_->have_dialog_force(dialog_id, "open_channel_recommended_channel") || + !td_->dialog_manager_->have_dialog_force(opened_dialog_id, "open_channel_recommended_channel")) { + return promise.set_error(Status::Error(400, "Chat not found")); + } + if (dialog_id.get_type() != DialogType::Channel || opened_dialog_id.get_type() != DialogType::Channel) { + return promise.set_error(Status::Error(400, "Invalid chat specified")); + } + vector> data; + data.push_back(telegram_api::make_object( + "ref_channel_id", make_tl_object(to_string(dialog_id.get_channel_id().get())))); + data.push_back(telegram_api::make_object( + "open_channel_id", make_tl_object(to_string(opened_dialog_id.get_channel_id().get())))); + save_app_log(td_, "channels.open_recommended_channel", DialogId(), + telegram_api::make_object(std::move(data)), std::move(promise)); +} + } // namespace td diff --git a/td/telegram/ChannelRecommendationManager.h b/td/telegram/ChannelRecommendationManager.h index d0ef82271..4fb431ce4 100644 --- a/td/telegram/ChannelRecommendationManager.h +++ b/td/telegram/ChannelRecommendationManager.h @@ -6,9 +6,19 @@ // #pragma once +#include "td/telegram/ChannelId.h" +#include "td/telegram/DialogId.h" +#include "td/telegram/td_api.h" +#include "td/telegram/telegram_api.h" + #include "td/actor/actor.h" #include "td/utils/common.h" +#include "td/utils/FlatHashMap.h" +#include "td/utils/Promise.h" +#include "td/utils/Status.h" + +#include namespace td { @@ -18,9 +28,59 @@ class ChannelRecommendationManager final : public Actor { public: ChannelRecommendationManager(Td *td, ActorShared<> parent); + void get_channel_recommendations(DialogId dialog_id, bool return_local, + Promise> &&chats_promise, + Promise> &&count_promise); + + void open_channel_recommended_channel(DialogId dialog_id, DialogId opened_dialog_id, Promise &&promise); + private: + static constexpr int32 CHANNEL_RECOMMENDATIONS_CACHE_TIME = 86400; // some reasonable limit + + struct RecommendedDialogs { + int32 total_count_ = 0; + vector dialog_ids_; + double next_reload_time_ = 0.0; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + }; + void tear_down() final; + bool is_suitable_recommended_channel(DialogId dialog_id) const; + + bool is_suitable_recommended_channel(ChannelId channel_id) const; + + bool are_suitable_recommended_dialogs(const RecommendedDialogs &recommended_dialogs) const; + + static string get_channel_recommendations_database_key(ChannelId channel_id); + + void load_channel_recommendations(ChannelId channel_id, bool use_database, bool return_local, + Promise> &&chats_promise, + Promise> &&count_promise); + + void fail_load_channel_recommendations_queries(ChannelId channel_id, Status &&error); + + void finish_load_channel_recommendations_queries(ChannelId channel_id, int32 total_count, + vector dialog_ids); + + void on_load_channel_recommendations_from_database(ChannelId channel_id, string value); + + void reload_channel_recommendations(ChannelId channel_id); + + void on_get_channel_recommendations( + ChannelId channel_id, Result>>> &&r_chats); + + FlatHashMap channel_recommended_dialogs_; + FlatHashMap>>, ChannelIdHash> + get_channel_recommendations_queries_; + FlatHashMap>>, ChannelIdHash> + get_channel_recommendation_count_queries_[2]; + Td *td_; ActorShared<> parent_; }; diff --git a/td/telegram/ContactsManager.cpp b/td/telegram/ContactsManager.cpp index d7c9d185b..327dafe61 100644 --- a/td/telegram/ContactsManager.cpp +++ b/td/telegram/ContactsManager.cpp @@ -7,7 +7,6 @@ #include "td/telegram/ContactsManager.h" #include "td/telegram/AnimationsManager.h" -#include "td/telegram/Application.h" #include "td/telegram/AuthManager.h" #include "td/telegram/BlockListId.h" #include "td/telegram/BotMenuButton.h" @@ -2266,55 +2265,6 @@ class MigrateChatQuery final : public Td::ResultHandler { } }; -class GetChannelRecommendationsQuery final : public Td::ResultHandler { - Promise>>> promise_; - ChannelId channel_id_; - - public: - explicit GetChannelRecommendationsQuery( - Promise>>> &&promise) - : promise_(std::move(promise)) { - } - - void send(ChannelId channel_id) { - channel_id_ = channel_id; - - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); - CHECK(input_channel != nullptr); - send_query( - G()->net_query_creator().create(telegram_api::channels_getChannelRecommendations(std::move(input_channel)))); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto chats_ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for GetChannelRecommendationsQuery: " << to_string(chats_ptr); - switch (chats_ptr->get_id()) { - case telegram_api::messages_chats::ID: { - auto chats = move_tl_object_as(chats_ptr); - auto total_count = static_cast(chats->chats_.size()); - return promise_.set_value({total_count, std::move(chats->chats_)}); - } - case telegram_api::messages_chatsSlice::ID: { - auto chats = move_tl_object_as(chats_ptr); - return promise_.set_value({chats->count_, std::move(chats->chats_)}); - } - default: - UNREACHABLE(); - return promise_.set_error(Status::Error("Unreachable")); - } - } - - void on_error(Status status) final { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelRecommendationsQuery"); - promise_.set_error(std::move(status)); - } -}; - class GetCreatedPublicChannelsQuery final : public Td::ResultHandler { Promise promise_; PublicDialogType type_; @@ -2806,9 +2756,6 @@ ContactsManager::ContactsManager(Td *td, ActorShared<> parent) : td_(td), parent } if (G()->use_sqlite_pmc()) { G()->td_db()->get_sqlite_pmc()->erase_by_prefix("us_bot_info", Auto()); - if (!G()->use_message_database()) { - G()->td_db()->get_sqlite_pmc()->erase_by_prefix("channel_recommendations", Auto()); - } } if (!td_->auth_manager_->is_bot()) { @@ -4331,42 +4278,6 @@ void ContactsManager::SecretChat::parse(ParserT &parser) { } } -template -void ContactsManager::RecommendedDialogs::store(StorerT &storer) const { - bool has_dialog_ids = !dialog_ids_.empty(); - bool has_total_count = static_cast(total_count_) != dialog_ids_.size(); - BEGIN_STORE_FLAGS(); - STORE_FLAG(has_dialog_ids); - STORE_FLAG(has_total_count); - END_STORE_FLAGS(); - if (has_dialog_ids) { - td::store(dialog_ids_, storer); - } - store_time(next_reload_time_, storer); - if (has_total_count) { - td::store(total_count_, storer); - } -} - -template -void ContactsManager::RecommendedDialogs::parse(ParserT &parser) { - bool has_dialog_ids; - bool has_total_count; - BEGIN_PARSE_FLAGS(); - PARSE_FLAG(has_dialog_ids); - PARSE_FLAG(has_total_count); - END_PARSE_FLAGS(); - if (has_dialog_ids) { - td::parse(dialog_ids_, parser); - } - parse_time(next_reload_time_, parser); - if (has_total_count) { - td::parse(total_count_, parser); - } else { - total_count_ = static_cast(dialog_ids_.size()); - } -} - Result> ContactsManager::get_input_user(UserId user_id) const { if (user_id == get_my_id()) { return make_tl_object(); @@ -7331,275 +7242,6 @@ vector ContactsManager::get_dialog_ids(vectoroption_manager_->get_option_boolean("is_premium"); - auto have_all = recommended_dialogs.dialog_ids_.size() == static_cast(recommended_dialogs.total_count_); - if (!have_all && is_premium) { - return false; - } - return true; -} - -void ContactsManager::get_channel_recommendations(DialogId dialog_id, bool return_local, - Promise> &&chats_promise, - Promise> &&count_promise) { - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "get_channel_recommendations")) { - if (chats_promise) { - chats_promise.set_error(Status::Error(400, "Chat not found")); - } - if (count_promise) { - count_promise.set_error(Status::Error(400, "Chat not found")); - } - return; - } - if (dialog_id.get_type() != DialogType::Channel) { - if (chats_promise) { - chats_promise.set_value(td_api::make_object()); - } - if (count_promise) { - count_promise.set_value(td_api::make_object(0)); - } - return; - } - auto channel_id = dialog_id.get_channel_id(); - if (!is_broadcast_channel(channel_id) || get_input_channel(channel_id) == nullptr) { - if (chats_promise) { - chats_promise.set_value(td_api::make_object()); - } - if (count_promise) { - count_promise.set_value(td_api::make_object(0)); - } - return; - } - bool use_database = true; - auto it = channel_recommended_dialogs_.find(channel_id); - if (it != channel_recommended_dialogs_.end()) { - if (are_suitable_recommended_dialogs(it->second)) { - auto next_reload_time = it->second.next_reload_time_; - if (chats_promise) { - chats_promise.set_value(td_->dialog_manager_->get_chats_object(it->second.total_count_, it->second.dialog_ids_, - "get_channel_recommendations")); - } - if (count_promise) { - count_promise.set_value(td_api::make_object(it->second.total_count_)); - } - if (next_reload_time > Time::now()) { - return; - } - chats_promise = {}; - count_promise = {}; - } else { - LOG(INFO) << "Drop cache for similar chats of " << dialog_id; - channel_recommended_dialogs_.erase(it); - if (G()->use_message_database()) { - G()->td_db()->get_sqlite_pmc()->erase(get_channel_recommendations_database_key(channel_id), Auto()); - } - } - use_database = false; - } - load_channel_recommendations(channel_id, use_database, return_local, std::move(chats_promise), - std::move(count_promise)); -} - -string ContactsManager::get_channel_recommendations_database_key(ChannelId channel_id) { - return PSTRING() << "channel_recommendations" << channel_id.get(); -} - -void ContactsManager::load_channel_recommendations(ChannelId channel_id, bool use_database, bool return_local, - Promise> &&chats_promise, - Promise> &&count_promise) { - if (count_promise) { - get_channel_recommendation_count_queries_[return_local][channel_id].push_back(std::move(count_promise)); - } - auto &queries = get_channel_recommendations_queries_[channel_id]; - queries.push_back(std::move(chats_promise)); - if (queries.size() == 1) { - if (G()->use_message_database() && use_database) { - G()->td_db()->get_sqlite_pmc()->get( - get_channel_recommendations_database_key(channel_id), - PromiseCreator::lambda([actor_id = actor_id(this), channel_id](string value) { - send_closure(actor_id, &ContactsManager::on_load_channel_recommendations_from_database, channel_id, - std::move(value)); - })); - } else { - reload_channel_recommendations(channel_id); - } - } -} - -void ContactsManager::fail_load_channel_recommendations_queries(ChannelId channel_id, Status &&error) { - for (int return_local = 0; return_local < 2; return_local++) { - auto it = get_channel_recommendation_count_queries_[return_local].find(channel_id); - if (it != get_channel_recommendation_count_queries_[return_local].end()) { - auto promises = std::move(it->second); - CHECK(!promises.empty()); - get_channel_recommendation_count_queries_[return_local].erase(it); - fail_promises(promises, error.clone()); - } - } - auto it = get_channel_recommendations_queries_.find(channel_id); - CHECK(it != get_channel_recommendations_queries_.end()); - auto promises = std::move(it->second); - CHECK(!promises.empty()); - get_channel_recommendations_queries_.erase(it); - fail_promises(promises, std::move(error)); -} - -void ContactsManager::finish_load_channel_recommendations_queries(ChannelId channel_id, int32 total_count, - vector dialog_ids) { - for (int return_local = 0; return_local < 2; return_local++) { - auto it = get_channel_recommendation_count_queries_[return_local].find(channel_id); - if (it != get_channel_recommendation_count_queries_[return_local].end()) { - auto promises = std::move(it->second); - CHECK(!promises.empty()); - get_channel_recommendation_count_queries_[return_local].erase(it); - for (auto &promise : promises) { - promise.set_value(td_api::make_object(total_count)); - } - } - } - auto it = get_channel_recommendations_queries_.find(channel_id); - CHECK(it != get_channel_recommendations_queries_.end()); - auto promises = std::move(it->second); - CHECK(!promises.empty()); - get_channel_recommendations_queries_.erase(it); - for (auto &promise : promises) { - if (promise) { - promise.set_value(td_->dialog_manager_->get_chats_object(total_count, dialog_ids, - "finish_load_channel_recommendations_queries")); - } - } -} - -void ContactsManager::on_load_channel_recommendations_from_database(ChannelId channel_id, string value) { - if (G()->close_flag()) { - return fail_load_channel_recommendations_queries(channel_id, G()->close_status()); - } - - if (value.empty()) { - return reload_channel_recommendations(channel_id); - } - auto &recommended_dialogs = channel_recommended_dialogs_[channel_id]; - if (log_event_parse(recommended_dialogs, value).is_error()) { - channel_recommended_dialogs_.erase(channel_id); - G()->td_db()->get_sqlite_pmc()->erase(get_channel_recommendations_database_key(channel_id), Auto()); - return reload_channel_recommendations(channel_id); - } - Dependencies dependencies; - for (auto dialog_id : recommended_dialogs.dialog_ids_) { - dependencies.add_dialog_and_dependencies(dialog_id); - } - if (!dependencies.resolve_force(td_, "on_load_channel_recommendations_from_database") || - !are_suitable_recommended_dialogs(recommended_dialogs)) { - channel_recommended_dialogs_.erase(channel_id); - G()->td_db()->get_sqlite_pmc()->erase(get_channel_recommendations_database_key(channel_id), Auto()); - return reload_channel_recommendations(channel_id); - } - - auto next_reload_time = recommended_dialogs.next_reload_time_; - finish_load_channel_recommendations_queries(channel_id, recommended_dialogs.total_count_, - recommended_dialogs.dialog_ids_); - - if (next_reload_time <= Time::now()) { - load_channel_recommendations(channel_id, false, false, Auto(), Auto()); - } -} - -void ContactsManager::reload_channel_recommendations(ChannelId channel_id) { - auto it = get_channel_recommendation_count_queries_[1].find(channel_id); - if (it != get_channel_recommendation_count_queries_[1].end()) { - auto promises = std::move(it->second); - CHECK(!promises.empty()); - get_channel_recommendation_count_queries_[1].erase(it); - for (auto &promise : promises) { - promise.set_value(td_api::make_object(-1)); - } - } - auto query_promise = - PromiseCreator::lambda([actor_id = actor_id(this), channel_id]( - Result>>> &&result) { - send_closure(actor_id, &ContactsManager::on_get_channel_recommendations, channel_id, std::move(result)); - }); - td_->create_handler(std::move(query_promise))->send(channel_id); -} - -void ContactsManager::on_get_channel_recommendations( - ChannelId channel_id, Result>>> &&r_chats) { - G()->ignore_result_if_closing(r_chats); - - if (r_chats.is_error()) { - return fail_load_channel_recommendations_queries(channel_id, r_chats.move_as_error()); - } - - auto chats = r_chats.move_as_ok(); - auto total_count = chats.first; - auto channel_ids = get_channel_ids(std::move(chats.second), "on_get_channel_recommendations"); - vector dialog_ids; - if (total_count < static_cast(channel_ids.size())) { - LOG(ERROR) << "Receive total_count = " << total_count << " and " << channel_ids.size() << " similar chats for " - << channel_id; - total_count = static_cast(channel_ids.size()); - } - for (auto recommended_channel_id : channel_ids) { - auto recommended_dialog_id = DialogId(recommended_channel_id); - td_->dialog_manager_->force_create_dialog(recommended_dialog_id, "on_get_channel_recommendations"); - if (is_suitable_recommended_channel(recommended_channel_id)) { - dialog_ids.push_back(recommended_dialog_id); - } else { - total_count--; - } - } - auto &recommended_dialogs = channel_recommended_dialogs_[channel_id]; - recommended_dialogs.total_count_ = total_count; - recommended_dialogs.dialog_ids_ = dialog_ids; - recommended_dialogs.next_reload_time_ = Time::now() + CHANNEL_RECOMMENDATIONS_CACHE_TIME; - - if (G()->use_message_database()) { - G()->td_db()->get_sqlite_pmc()->set(get_channel_recommendations_database_key(channel_id), - log_event_store(recommended_dialogs).as_slice().str(), Promise()); - } - - finish_load_channel_recommendations_queries(channel_id, total_count, std::move(dialog_ids)); -} - -void ContactsManager::open_channel_recommended_channel(DialogId dialog_id, DialogId opened_dialog_id, - Promise &&promise) { - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "open_channel_recommended_channel") || - !td_->dialog_manager_->have_dialog_force(opened_dialog_id, "open_channel_recommended_channel")) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - if (dialog_id.get_type() != DialogType::Channel || opened_dialog_id.get_type() != DialogType::Channel) { - return promise.set_error(Status::Error(400, "Invalid chat specified")); - } - vector> data; - data.push_back(telegram_api::make_object( - "ref_channel_id", make_tl_object(to_string(dialog_id.get_channel_id().get())))); - data.push_back(telegram_api::make_object( - "open_channel_id", make_tl_object(to_string(opened_dialog_id.get_channel_id().get())))); - save_app_log(td_, "channels.open_recommended_channel", DialogId(), - telegram_api::make_object(std::move(data)), std::move(promise)); -} - void ContactsManager::return_created_public_dialogs(Promise> &&promise, const vector &channel_ids) { if (!promise) { @@ -10503,9 +10145,6 @@ void ContactsManager::update_channel(Channel *c, ChannelId channel_id, bool from if (c->had_read_access && !have_read_access) { send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_deleted, DialogId(channel_id), Promise()); - if (G()->use_message_database()) { - G()->td_db()->get_sqlite_pmc()->erase(get_channel_recommendations_database_key(channel_id), Promise()); - } } else if (!from_database && c->was_member != is_member) { DialogId dialog_id(channel_id); send_closure_later(G()->messages_manager(), &MessagesManager::force_create_dialog, dialog_id, "update channel", diff --git a/td/telegram/ContactsManager.h b/td/telegram/ContactsManager.h index 9b982c029..89910e93e 100644 --- a/td/telegram/ContactsManager.h +++ b/td/telegram/ContactsManager.h @@ -528,15 +528,9 @@ class ContactsManager final : public Actor { void migrate_dialog_to_megagroup(DialogId dialog_id, Promise> &&promise); - void get_channel_recommendations(DialogId dialog_id, bool return_local, - Promise> &&chats_promise, - Promise> &&count_promise); - void get_created_public_dialogs(PublicDialogType type, Promise> &&promise, bool from_binlog); - void open_channel_recommended_channel(DialogId dialog_id, DialogId opened_dialog_id, Promise &&promise); - void check_created_public_dialogs_limit(PublicDialogType type, Promise &&promise); void reload_created_public_dialogs(PublicDialogType type, Promise> &&promise); @@ -1127,18 +1121,6 @@ class ContactsManager final : public Actor { vector pending_requests; }; - struct RecommendedDialogs { - int32 total_count_ = 0; - vector dialog_ids_; - double next_reload_time_ = 0.0; - - template - void store(StorerT &storer) const; - - template - void parse(ParserT &parser); - }; - class UserLogEvent; class ChatLogEvent; class ChannelLogEvent; @@ -1149,8 +1131,7 @@ class ContactsManager final : public Actor { static constexpr size_t MAX_TITLE_LENGTH = 128; // server side limit for chat title static constexpr size_t MAX_DESCRIPTION_LENGTH = 255; // server side limit for chat/channel description - static constexpr int32 MAX_ACTIVE_STORY_ID_RELOAD_TIME = 3600; // some reasonable limit - static constexpr int32 CHANNEL_RECOMMENDATIONS_CACHE_TIME = 86400; // some reasonable limit + static constexpr int32 MAX_ACTIVE_STORY_ID_RELOAD_TIME = 3600; // some reasonable limit // the True fields aren't set for manually created telegram_api::user objects, therefore the flags must be used static constexpr int32 USER_FLAG_HAS_ACCESS_HASH = 1 << 0; @@ -1623,30 +1604,6 @@ class ContactsManager final : public Actor { void on_clear_imported_contacts(vector &&contacts, vector contacts_unique_id, std::pair, vector> &&to_add, Promise &&promise); - bool is_suitable_recommended_channel(DialogId dialog_id) const; - - bool is_suitable_recommended_channel(ChannelId channel_id) const; - - bool are_suitable_recommended_dialogs(const RecommendedDialogs &recommended_dialogs) const; - - static string get_channel_recommendations_database_key(ChannelId channel_id); - - void load_channel_recommendations(ChannelId channel_id, bool use_database, bool return_local, - Promise> &&chats_promise, - Promise> &&count_promise); - - void fail_load_channel_recommendations_queries(ChannelId channel_id, Status &&error); - - void finish_load_channel_recommendations_queries(ChannelId channel_id, int32 total_count, - vector dialog_ids); - - void on_load_channel_recommendations_from_database(ChannelId channel_id, string value); - - void reload_channel_recommendations(ChannelId channel_id); - - void on_get_channel_recommendations(ChannelId channel_id, - Result>>> &&r_chats); - static bool is_channel_public(const Channel *c); static bool is_suitable_created_public_channel(PublicDialogType type, const Channel *c); @@ -1804,12 +1761,6 @@ class ContactsManager final : public Actor { FlatHashMap, UserIdHash> secret_chats_with_user_; - FlatHashMap channel_recommended_dialogs_; - FlatHashMap>>, ChannelIdHash> - get_channel_recommendations_queries_; - FlatHashMap>>, ChannelIdHash> - get_channel_recommendation_count_queries_[2]; - bool created_public_channels_inited_[2] = {false, false}; vector created_public_channels_[2]; vector>> get_created_public_channels_queries_[2]; diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index 8ce9d190d..e0856ebc6 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -5041,21 +5041,22 @@ void Td::on_request(uint64 id, const td_api::clearAutosaveSettingsExceptions &re void Td::on_request(uint64 id, const td_api::getChatSimilarChats &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - contacts_manager_->get_channel_recommendations(DialogId(request.chat_id_), false, std::move(promise), Auto()); + channel_recommendation_manager_->get_channel_recommendations(DialogId(request.chat_id_), false, std::move(promise), + Auto()); } void Td::on_request(uint64 id, const td_api::getChatSimilarChatCount &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - contacts_manager_->get_channel_recommendations(DialogId(request.chat_id_), request.return_local_, Auto(), - std::move(promise)); + channel_recommendation_manager_->get_channel_recommendations(DialogId(request.chat_id_), request.return_local_, + Auto(), std::move(promise)); } void Td::on_request(uint64 id, const td_api::openChatSimilarChat &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->open_channel_recommended_channel(DialogId(request.chat_id_), DialogId(request.opened_chat_id_), - std::move(promise)); + channel_recommendation_manager_->open_channel_recommended_channel( + DialogId(request.chat_id_), DialogId(request.opened_chat_id_), std::move(promise)); } void Td::on_request(uint64 id, const td_api::getTopChats &request) {