diff options
| -rw-r--r-- | td/generate/scheme/td_api.tl | 12 | ||||
| -rw-r--r-- | td/telegram/ChannelId.h | 6 | ||||
| -rw-r--r-- | td/telegram/ChatManager.h | 1 | ||||
| -rw-r--r-- | td/telegram/CommunityManager.cpp | 584 | ||||
| -rw-r--r-- | td/telegram/CommunityManager.h | 112 | ||||
| -rw-r--r-- | td/telegram/FileReferenceManager.cpp | 15 | ||||
| -rw-r--r-- | td/telegram/Td.cpp | 7 | ||||
| -rw-r--r-- | td/telegram/TdDb.cpp | 3 | ||||
| -rw-r--r-- | td/telegram/TdDb.h | 1 | ||||
| -rw-r--r-- | td/telegram/logevent/LogEvent.h | 1 |
10 files changed, 738 insertions, 4 deletions
diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index 6f7c61173..027650222 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -2259,6 +2259,15 @@ profileAccentColors palette_colors:vector<int32> background_colors:vector<int32> profileAccentColor id:int32 light_theme_colors:profileAccentColors dark_theme_colors:profileAccentColors min_supergroup_chat_boost_level:int32 min_channel_chat_boost_level:int32 = ProfileAccentColor; +//@description Represents a community consisting of supergroup chats, channel chats and chats with bots +//@id Community identifier +//@have_access If false, the community is inaccessible, and the only information known about the community is inside this class. Identifier of the community can't be passed to any method +//@name Community name +//@photo Community photo; may be null +//@date Point in time (Unix timestamp) when the community was joined, or the point in time when the community was created, in case the user is not a member of any chat in the community +community id:int53 have_access:Bool name:string photo:chatPhotoInfo date:int32 = Community; + + //@description Contains description of user rating //@level The level of the user; may be negative //@is_maximum_level_reached True, if the maximum level is reached @@ -10550,6 +10559,9 @@ updateChatAction chat_id:int53 topic_id:MessageTopic sender_id:MessageSender act //@content Content of the message; always of the type messageText or messageRichMessage updatePendingMessage chat_id:int53 forum_topic_id:int32 draft_id:int64 content:MessageContent = Update; +//@description Some data of a community has changed. This update is guaranteed to come before the community identifier is returned to the application @community New data about the community +updateCommunity community:community = Update; + //@description The user went online or offline @user_id User identifier @status New status of the user updateUserStatus user_id:int53 status:UserStatus = Update; diff --git a/td/telegram/ChannelId.h b/td/telegram/ChannelId.h index d1b06a9c4..dbf94ce64 100644 --- a/td/telegram/ChannelId.h +++ b/td/telegram/ChannelId.h @@ -33,7 +33,11 @@ class ChannelId { ChannelId(T channel_id) = delete; bool is_valid() const { - return (0 < id && id < MAX_CHANNEL_ID) || (MIN_MONOFORUM_CHANNEL_ID <= id && id < MAX_MONOFORUM_CHANNEL_ID); + return is_regular_channel() || (MIN_MONOFORUM_CHANNEL_ID <= id && id < MAX_MONOFORUM_CHANNEL_ID); + } + + bool is_regular_channel() const { + return 0 < id && id < MAX_CHANNEL_ID; } int64 get() const { diff --git a/td/telegram/ChatManager.h b/td/telegram/ChatManager.h index 5c728b756..721f35765 100644 --- a/td/telegram/ChatManager.h +++ b/td/telegram/ChatManager.h @@ -363,6 +363,7 @@ class ChatManager final : public Actor { bool have_channel_force(ChannelId channel_id, const char *source); bool get_channel(ChannelId channel_id, int left_tries, Promise<Unit> &&promise); void reload_channel(ChannelId channel_id, Promise<Unit> &&promise, const char *source); + void load_channel_full(ChannelId channel_id, bool force, Promise<Unit> &&promise, const char *source); FileSourceId get_channel_full_file_source_id(ChannelId channel_id); void reload_channel_full(ChannelId channel_id, Promise<Unit> &&promise, const char *source); diff --git a/td/telegram/CommunityManager.cpp b/td/telegram/CommunityManager.cpp index 3c5625b37..c28e997ed 100644 --- a/td/telegram/CommunityManager.cpp +++ b/td/telegram/CommunityManager.cpp @@ -6,8 +6,69 @@ // #include "td/telegram/CommunityManager.h" +#include "td/telegram/AuthManager.h" +#include "td/telegram/DialogPhoto.hpp" +#include "td/telegram/Global.h" +#include "td/telegram/logevent/LogEvent.h" +#include "td/telegram/logevent/LogEventHelper.h" +#include "td/telegram/PhotoSize.h" +#include "td/telegram/Td.h" +#include "td/telegram/TdDb.h" + +#include "td/db/binlog/BinlogEvent.h" +#include "td/db/binlog/BinlogHelper.h" +#include "td/db/SqliteKeyValue.h" +#include "td/db/SqliteKeyValueAsync.h" + +#include "td/utils/logging.h" +#include "td/utils/utf8.h" + namespace td { +template <class StorerT> +void CommunityManager::Community::store(StorerT &storer) const { + using td::store; + bool has_photo = photo.small_file_id.is_valid(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_photo); + STORE_FLAG(collapsed_in_dialogs); + END_STORE_FLAGS(); + store(access_hash, storer); + store(title, storer); + store(date, storer); + store(status, storer); + store(cache_version, storer); + store(default_permissions, storer); + if (has_photo) { + store(photo, storer); + } +} + +template <class ParserT> +void CommunityManager::Community::parse(ParserT &parser) { + using td::parse; + bool has_photo = photo.small_file_id.is_valid(); + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_photo); + PARSE_FLAG(collapsed_in_dialogs); + END_PARSE_FLAGS(); + parse(access_hash, parser); + parse(title, parser); + parse(date, parser); + parse(status, parser); + parse(cache_version, parser); + parse(default_permissions, parser); + if (has_photo) { + parse(photo, parser); + } + + if (!check_utf8(title)) { + LOG(ERROR) << "Have invalid title \"" << title << '"'; + title.clear(); + cache_version = 0; + } +} + CommunityManager::CommunityManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { } @@ -15,4 +76,527 @@ void CommunityManager::tear_down() { parent_.reset(); } +class CommunityManager::CommunityLogEvent { + public: + CommunityId community_id; + const Community *c_in = nullptr; + unique_ptr<Community> c_out; + + CommunityLogEvent() = default; + + CommunityLogEvent(CommunityId community_id, const Community *c) : community_id(community_id), c_in(c) { + } + + template <class StorerT> + void store(StorerT &storer) const { + td::store(community_id, storer); + td::store(*c_in, storer); + } + + template <class ParserT> + void parse(ParserT &parser) { + td::parse(community_id, parser); + td::parse(c_out, parser); + } +}; + +void CommunityManager::save_community(Community *c, CommunityId community_id, bool from_binlog) { + if (!G()->use_chat_info_database()) { + return; + } + CHECK(c != nullptr); + if (!c->is_saved) { + if (!from_binlog) { + auto log_event = CommunityLogEvent(community_id, c); + auto storer = get_log_event_storer(log_event); + if (c->log_event_id == 0) { + c->log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::Communities, storer); + } else { + binlog_rewrite(G()->td_db()->get_binlog(), c->log_event_id, LogEvent::HandlerType::Communities, storer); + } + } + + save_community_to_database(c, community_id); + return; + } +} + +void CommunityManager::on_binlog_community_event(BinlogEvent &&event) { + if (!G()->use_chat_info_database()) { + binlog_erase(G()->td_db()->get_binlog(), event.id_); + return; + } + + CommunityLogEvent log_event; + if (log_event_parse(log_event, event.get_data()).is_error()) { + LOG(ERROR) << "Failed to load a community from binlog"; + binlog_erase(G()->td_db()->get_binlog(), event.id_); + return; + } + + auto community_id = log_event.community_id; + if (have_community(community_id) || !community_id.is_valid()) { + LOG(ERROR) << "Skip adding already added " << community_id; + binlog_erase(G()->td_db()->get_binlog(), event.id_); + return; + } + + LOG(INFO) << "Add " << community_id << " from binlog"; + communities_.set(community_id, std::move(log_event.c_out)); + + Community *c = get_community(community_id); + CHECK(c != nullptr); + c->log_event_id = event.id_; + + update_community(c, community_id, true, false); +} + +string CommunityManager::get_community_database_key(CommunityId community_id) { + return PSTRING() << "community" << community_id.get(); +} + +string CommunityManager::get_community_database_value(const Community *c) { + return log_event_store(*c).as_slice().str(); +} + +void CommunityManager::save_community_to_database(Community *c, CommunityId community_id) { + CHECK(c != nullptr); + if (c->is_being_saved) { + return; + } + if (loaded_from_database_communities_.count(community_id)) { + save_community_to_database_impl(c, community_id, get_community_database_value(c)); + return; + } + if (load_community_from_database_queries_.count(community_id) != 0) { + return; + } + + load_community_from_database_impl(community_id, false, Auto()); +} + +void CommunityManager::save_community_to_database_impl(Community *c, CommunityId community_id, string value) { + CHECK(c != nullptr); + CHECK(load_community_from_database_queries_.count(community_id) == 0); + CHECK(!c->is_being_saved); + c->is_being_saved = true; + c->is_saved = true; + LOG(INFO) << "Trying to save to database " << community_id; + G()->td_db()->get_sqlite_pmc()->set(get_community_database_key(community_id), std::move(value), + PromiseCreator::lambda([community_id](Result<> result) { + send_closure(G()->community_manager(), + &CommunityManager::on_save_community_to_database, community_id, + result.is_ok()); + })); +} + +void CommunityManager::on_save_community_to_database(CommunityId community_id, bool success) { + if (G()->close_flag()) { + return; + } + + Community *c = get_community(community_id); + CHECK(c != nullptr); + CHECK(c->is_being_saved); + CHECK(load_community_from_database_queries_.count(community_id) == 0); + c->is_being_saved = false; + + if (!success) { + LOG(ERROR) << "Failed to save " << community_id << " to database"; + c->is_saved = false; + } else { + LOG(INFO) << "Successfully saved " << community_id << " to database"; + } + if (c->is_saved) { + if (c->log_event_id != 0) { + binlog_erase(G()->td_db()->get_binlog(), c->log_event_id); + c->log_event_id = 0; + } + } else { + save_community(c, community_id, c->log_event_id != 0); + } +} + +void CommunityManager::load_community_from_database(Community *c, CommunityId community_id, Promise<Unit> promise) { + if (loaded_from_database_communities_.count(community_id)) { + promise.set_value(Unit()); + return; + } + + CHECK(c == nullptr || !c->is_being_saved); + load_community_from_database_impl(community_id, false, std::move(promise)); +} + +void CommunityManager::load_community_from_database_impl(CommunityId community_id, bool is_recursive, + Promise<Unit> promise) { + LOG(INFO) << "Load " << community_id << " from database"; + auto &load_community_queries = load_community_from_database_queries_[community_id]; + load_community_queries.push_back(std::move(promise)); + if (load_community_queries.size() == 1u) { + G()->td_db()->get_sqlite_pmc()->get( + get_community_database_key(community_id), PromiseCreator::lambda([community_id, is_recursive](string value) { + send_closure(G()->community_manager(), &CommunityManager::on_load_community_from_database, community_id, + std::move(value), false, is_recursive); + })); + } +} + +void CommunityManager::on_load_community_from_database(CommunityId community_id, string value, bool force, + bool is_recursive) { + if (G()->close_flag() && !force) { + // the community is in Binlog and will be saved after restart + return; + } + + CHECK(community_id.is_valid()); + if (!loaded_from_database_communities_.insert(community_id).second) { + return; + } + + auto it = load_community_from_database_queries_.find(community_id); + vector<Promise<Unit>> promises; + if (it != load_community_from_database_queries_.end()) { + promises = std::move(it->second); + CHECK(!promises.empty()); + load_community_from_database_queries_.erase(it); + } + + LOG(INFO) << "Successfully loaded " << community_id << " of size " << value.size() << " from database"; + // G()->td_db()->get_sqlite_pmc()->erase(get_community_database_key(community_id), Auto()); + // return; + + Community *c = get_community(community_id); + if (c == nullptr) { + if (!value.empty()) { + c = add_community(community_id, "on_load_community_from_database"); + + if (log_event_parse(*c, value).is_error()) { + LOG(ERROR) << "Failed to load " << community_id << " from database"; + communities_.erase(community_id); + } else { + c->is_saved = true; + update_community(c, community_id, true, true); + } + } + } else { + CHECK(!c->is_saved); // community can't be saved before load completes + CHECK(!c->is_being_saved); + auto new_value = get_community_database_value(c); + if (value != new_value) { + save_community_to_database_impl(c, community_id, std::move(new_value)); + } else if (c->log_event_id != 0) { + binlog_erase(G()->td_db()->get_binlog(), c->log_event_id); + c->log_event_id = 0; + } + } + set_promises(promises); +} + +bool CommunityManager::have_community(CommunityId community_id) const { + return communities_.count(community_id) > 0; +} + +const CommunityManager::Community *CommunityManager::get_community(CommunityId community_id) const { + return communities_.get_pointer(community_id); +} + +CommunityManager::Community *CommunityManager::get_community(CommunityId community_id) { + return communities_.get_pointer(community_id); +} + +CommunityManager::Community *CommunityManager::add_community(CommunityId community_id, const char *source) { + CHECK(community_id.is_valid()); + auto &community_ptr = communities_[community_id]; + if (community_ptr == nullptr) { + community_ptr = make_unique<Community>(); + } + return community_ptr.get(); +} + +void CommunityManager::reload_community(CommunityId community_id, Promise<Unit> &&promise, const char *source) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + if (!community_id.is_valid()) { + return promise.set_error(400, "Invalid community identifier"); + } + + have_community_force(community_id, source); + //get_community_queries_.add_query(community_id.get(), std::move(promise), source); +} + +bool CommunityManager::have_community_force(CommunityId community_id, const char *source) { + return get_community_force(community_id, source) != nullptr; +} + +CommunityManager::Community *CommunityManager::get_community_force(CommunityId community_id, const char *source, + bool is_recursive) { + if (!community_id.is_valid()) { + return nullptr; + } + + Community *c = get_community(community_id); + if (c != nullptr) { + return c; + } + if (!G()->use_chat_info_database()) { + return nullptr; + } + if (loaded_from_database_communities_.count(community_id)) { + return nullptr; + } + + LOG(INFO) << "Trying to load " << community_id << " from database from " << source; + on_load_community_from_database(community_id, + G()->td_db()->get_sqlite_sync_pmc()->get(get_community_database_key(community_id)), + true, is_recursive); + return get_community(community_id); +} + +void CommunityManager::update_community(Community *c, CommunityId community_id, bool from_binlog, bool from_database) { + CHECK(c != nullptr); + + if (c->is_being_updated) { + LOG(ERROR) << "Detected recursive update of " << community_id; + } + c->is_being_updated = true; + SCOPE_EXIT { + c->is_being_updated = false; + }; + + LOG(DEBUG) << "Update " << community_id << ": need_save_to_database = " << c->need_save_to_database + << ", is_changed = " << c->is_changed; + c->need_save_to_database |= c->is_changed; + if (c->need_save_to_database) { + if (!from_database) { + c->is_saved = false; + } + c->need_save_to_database = false; + } + if (c->is_changed) { + send_closure(G()->td(), &Td::send_update, get_update_community_object(community_id, c)); + c->is_changed = false; + c->is_update_community_sent = true; + } + + if (!from_database) { + save_community(c, community_id, from_binlog); + } + + if (c->cache_version != Community::CACHE_VERSION && !c->is_repaired && !c->status.is_banned() && + c->access_hash != 0 && !G()->close_flag()) { + c->is_repaired = true; + + LOG(INFO) << "Repairing cache of " << community_id; + reload_community(community_id, Promise<Unit>(), "update_community"); + } +} + +void CommunityManager::on_get_community(telegram_api::community &community, const char *source) { + CommunityId community_id(community.id_); + if (!community_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << community_id << " from " << source << ": " << to_string(community); + return; + } + + if (community.flags_ == 0 && community.access_hash_ == 0 && community.title_.empty()) { + Community *c = get_community_force(community_id, source); + if (c != nullptr) { + LOG(ERROR) << "Receive from " << source << " empty " << community_id << ": " << to_string(community) << ", have " + << to_string(get_community_object(community_id, c)); + } + return; + } + + bool is_min = community.min_; + auto access_hash = community.access_hash_; + if (access_hash == 0 && !is_min) { + LOG(ERROR) << "Receive non-min " << community_id << " without access_hash from " << source; + return; + } + DialogParticipantStatus status = [&] { + if (community.creator_) { + return DialogParticipantStatus::Creator(!community.left_, false, string()); + } else if (community.admin_rights_ != nullptr) { + return DialogParticipantStatus(false, std::move(community.admin_rights_), string(), ChannelType::Unknown); + } else if (community.left_) { + return DialogParticipantStatus::Left(); + } else { + return DialogParticipantStatus::Member(0, string()); + } + }(); + Community *c = add_community(community_id, "on_get_community"); + if (c->access_hash != access_hash && (!is_min || c->access_hash == 0)) { + c->access_hash = access_hash; + if (access_hash == 0 || c->access_hash == 0) { + c->is_changed = true; + } else { + c->need_save_to_database = true; + } + } + if (c->date != community.date_ && (!is_min || c->date == 0)) { + c->date = community.date_; + c->is_changed = true; + } + if (c->collapsed_in_dialogs != community.collapsed_in_dialogs_ && !is_min) { + c->collapsed_in_dialogs = community.collapsed_in_dialogs_; + c->need_save_to_database = true; + } + if (!is_min) { + on_update_community_status(c, community_id, std::move(status)); + } + on_update_community_title(c, community_id, std::move(community.title_)); + if (!c->status.is_banned()) { + on_update_community_photo(c, community_id, std::move(community.photo_)); + } + on_update_community_default_permissions(c, community_id, + RestrictedRights(community.default_banned_rights_, ChannelType::Megagroup)); + + if (c->cache_version != Community::CACHE_VERSION) { + c->cache_version = Community::CACHE_VERSION; + c->need_save_to_database = true; + } + c->is_received_from_server = true; + update_community(c, community_id); +} + +void CommunityManager::on_get_community_forbidden(telegram_api::communityForbidden &community, const char *source) { + CommunityId community_id(community.id_); + if (!community_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << community_id << " from " << source << ": " << to_string(community); + return; + } + + if (community.flags_ == 0 && community.access_hash_ == 0 && community.title_.empty()) { + Community *c = get_community_force(community_id, source); + if (c != nullptr) { + LOG(ERROR) << "Receive from " << source << " empty " << community_id << ": " << to_string(community) << ", have " + << to_string(get_community_object(community_id, c)); + } + return; + } + + auto access_hash = community.access_hash_; + Community *c = add_community(community_id, "on_get_community"); + if (c->access_hash != access_hash) { + c->access_hash = access_hash; + if (access_hash == 0 || c->access_hash == 0) { + c->is_changed = true; + } else { + c->need_save_to_database = true; + } + } + if (c->date != 0) { + c->date = 0; + c->is_changed = true; + } + on_update_community_status(c, community_id, DialogParticipantStatus::Banned(0, string())); + on_update_community_title(c, community_id, std::move(community.title_)); + on_update_community_photo(c, community_id, nullptr); + + telegram_api::object_ptr<telegram_api::chatBannedRights> banned_rights; // == nullptr + on_update_community_default_permissions(c, community_id, RestrictedRights(banned_rights, ChannelType::Megagroup)); + + if (c->cache_version != Community::CACHE_VERSION) { + c->cache_version = Community::CACHE_VERSION; + c->need_save_to_database = true; + } + c->is_received_from_server = true; + update_community(c, community_id); +} + +void CommunityManager::on_update_community_photo(Community *c, CommunityId community_id, + telegram_api::object_ptr<telegram_api::ChatPhoto> &&chat_photo_ptr) { + on_update_community_photo(c, community_id, + get_dialog_photo(td_->file_manager_.get(), DialogId(ChannelId(community_id.get())), + c->access_hash, std::move(chat_photo_ptr)), + true); +} + +void CommunityManager::on_update_community_photo(Community *c, CommunityId community_id, DialogPhoto &&photo, + bool invalidate_photo_cache) { + if (td_->auth_manager_->is_bot()) { + photo.minithumbnail.clear(); + } + + if (need_update_dialog_photo(c->photo, photo)) { + LOG(DEBUG) << "Update photo of " << community_id << " from " << c->photo << " to " << photo; + c->photo = std::move(photo); + c->is_changed = true; + + if (invalidate_photo_cache) { + /* + auto community_full = get_community_full(community_id, true, "on_update_community_photo"); // must not load CommunityFull + if (community_full != nullptr) { + on_update_community_full_photo(community_full, community_id, Photo()); + if (c->photo.small_file_id.is_valid()) { + if (community_full->expires_at > 0.0) { + community_full->expires_at = 0.0; + community_full->need_save_to_database = true; + } + reload_community_full(community_id, Auto(), "on_update_community_photo"); + } + update_community_full(community_full, community_id, "on_update_community_photo"); + } + */ + } + } else if (need_update_dialog_photo_minithumbnail(c->photo.minithumbnail, photo.minithumbnail)) { + c->photo.minithumbnail = std::move(photo.minithumbnail); + c->is_changed = true; + } +} + +void CommunityManager::on_update_community_title(Community *c, CommunityId community_id, string &&title) { + if (c->title != title) { + c->title = std::move(title); + c->is_changed = true; + } +} + +void CommunityManager::on_update_community_status(Community *c, CommunityId community_id, + DialogParticipantStatus &&status) { + if (c->status != status) { + LOG(INFO) << "Update " << community_id << " status from " << c->status << " to " << status; + c->status = status; + c->is_changed = true; + } +} + +void CommunityManager::on_update_community_default_permissions(Community *c, CommunityId community_id, + RestrictedRights default_permissions) { + if (c->default_permissions != default_permissions) { + LOG(INFO) << "Update " << community_id << " default permissions from " << c->default_permissions << " to " + << default_permissions; + c->default_permissions = default_permissions; + c->is_changed = true; + } +} + +td_api::object_ptr<td_api::community> CommunityManager::get_community_object(CommunityId community_id) const { + return get_community_object(community_id, get_community(community_id)); +} + +td_api::object_ptr<td_api::community> CommunityManager::get_community_object(CommunityId community_id, + const Community *c) const { + if (c == nullptr) { + return nullptr; + } + return td_api::make_object<td_api::community>(community_id.get(), c->access_hash != 0, c->title, + get_chat_photo_info_object(td_->file_manager_.get(), &c->photo), + c->date); +} + +td_api::object_ptr<td_api::updateCommunity> CommunityManager::get_update_community_object(CommunityId community_id, + const Community *c) const { + if (c == nullptr) { + return get_update_unknown_community_object(community_id); + } + return td_api::make_object<td_api::updateCommunity>(get_community_object(community_id, c)); +} + +td_api::object_ptr<td_api::updateCommunity> CommunityManager::get_update_unknown_community_object( + CommunityId community_id) const { + return td_api::make_object<td_api::updateCommunity>( + td_api::make_object<td_api::community>(community_id.get(), false, string(), nullptr, 0)); +} + } // namespace td diff --git a/td/telegram/CommunityManager.h b/td/telegram/CommunityManager.h index 6e3b2ce46..e9df07d79 100644 --- a/td/telegram/CommunityManager.h +++ b/td/telegram/CommunityManager.h @@ -6,23 +6,135 @@ // #pragma once +#include "td/telegram/CommunityId.h" +#include "td/telegram/DialogParticipant.h" +#include "td/telegram/DialogPhoto.h" + #include "td/actor/actor.h" #include "td/utils/common.h" +#include "td/utils/FlatHashMap.h" +#include "td/utils/FlatHashSet.h" +#include "td/utils/Promise.h" +#include "td/utils/WaitFreeHashMap.h" namespace td { +struct BinlogEvent; class Td; class CommunityManager final : public Actor { public: CommunityManager(Td *td, ActorShared<> parent); + bool have_community(CommunityId community_id) const; + + bool have_community_force(CommunityId community_id, const char *source); + + void reload_community(CommunityId community_id, Promise<Unit> &&promise, const char *source); + + void on_get_community(telegram_api::community &community, const char *source); + + void on_get_community_forbidden(telegram_api::communityForbidden &community, const char *source); + + td_api::object_ptr<td_api::community> get_community_object(CommunityId community_id) const; + + void on_binlog_community_event(BinlogEvent &&event); + private: + struct Community { + int64 access_hash = 0; + string title; + DialogPhoto photo; + DialogParticipantStatus status = DialogParticipantStatus::Banned(0, string()); + RestrictedRights default_permissions = RestrictedRights::restrict_all(); + int32 date = 0; + bool collapsed_in_dialogs = false; + + static constexpr uint32 CACHE_VERSION = 1; + uint32 cache_version = 0; + + bool is_being_updated = false; + bool is_changed = true; // have new changes that need to be sent to the client and database + bool need_save_to_database = true; // have new changes that need only to be saved to the database + bool is_update_community_sent = false; + + bool is_repaired = false; // whether cached value is rechecked + + bool is_saved = false; // is current community version being saved/is saved to the database + bool is_being_saved = false; // is current community being saved to the database + + bool is_received_from_server = false; // true, if the community was received from the server and not the database + + uint64 log_event_id = 0; + + template <class StorerT> + void store(StorerT &storer) const; + + template <class ParserT> + void parse(ParserT &parser); + }; + + class CommunityLogEvent; + void tear_down() final; + void save_community(Community *c, CommunityId community_id, bool from_binlog); + + static string get_community_database_key(CommunityId community_id); + + static string get_community_database_value(const Community *c); + + void save_community_to_database(Community *c, CommunityId community_id); + + void save_community_to_database_impl(Community *c, CommunityId community_id, string value); + + void on_save_community_to_database(CommunityId community_id, bool success); + + void load_community_from_database(Community *c, CommunityId community_id, Promise<Unit> promise); + + void load_community_from_database_impl(CommunityId community_id, bool is_recursive, Promise<Unit> promise); + + void on_load_community_from_database(CommunityId community_id, string value, bool force, bool is_recursive); + + void update_community(Community *c, CommunityId community_id, bool from_binlog = false, bool from_database = false); + + const Community *get_community(CommunityId community_id) const; + + Community *get_community(CommunityId community_id); + + Community *get_community_force(CommunityId community_id, const char *source, bool is_recursive = false); + + Community *add_community(CommunityId community_id, const char *source); + + void on_update_community_photo(Community *c, CommunityId community_id, + telegram_api::object_ptr<telegram_api::ChatPhoto> &&chat_photo_ptr); + + void on_update_community_photo(Community *c, CommunityId community_id, DialogPhoto &&photo, + bool invalidate_photo_cache); + + static void on_update_community_title(Community *c, CommunityId community_id, string &&title); + + void on_update_community_status(Community *c, CommunityId community_id, DialogParticipantStatus &&status); + + static void on_update_community_default_permissions(Community *c, CommunityId community_id, + RestrictedRights default_permissions); + + td_api::object_ptr<td_api::updateCommunity> get_update_community_object(CommunityId community_id, + const Community *c) const; + + td_api::object_ptr<td_api::updateCommunity> get_update_unknown_community_object(CommunityId community_id) const; + + td_api::object_ptr<td_api::community> get_community_object(CommunityId Community_id, const Community *c) const; + Td *td_; ActorShared<> parent_; + + WaitFreeHashMap<CommunityId, unique_ptr<Community>, CommunityIdHash> communities_; + mutable FlatHashSet<CommunityId, CommunityIdHash> unknown_communities_; + + FlatHashMap<CommunityId, vector<Promise<Unit>>, CommunityIdHash> load_community_from_database_queries_; + FlatHashSet<CommunityId, CommunityIdHash> loaded_from_database_communities_; }; } // namespace td diff --git a/td/telegram/FileReferenceManager.cpp b/td/telegram/FileReferenceManager.cpp index e46b3d578..f54166a10 100644 --- a/td/telegram/FileReferenceManager.cpp +++ b/td/telegram/FileReferenceManager.cpp @@ -12,6 +12,8 @@ #include "td/telegram/BackgroundManager.h" #include "td/telegram/BotInfoManager.h" #include "td/telegram/ChatManager.h" +#include "td/telegram/CommunityId.h" +#include "td/telegram/CommunityManager.h" #include "td/telegram/ConfigManager.h" #include "td/telegram/DialogManager.h" #include "td/telegram/DraftMessageManager.h" @@ -648,9 +650,18 @@ void FileReferenceManager::reload_photo(PhotoSizeSource source, Promise<Unit> pr case PhotoSizeSource::Type::DialogPhotoBig: case PhotoSizeSource::Type::DialogPhotoSmall: case PhotoSizeSource::Type::DialogPhotoBigLegacy: - case PhotoSizeSource::Type::DialogPhotoSmallLegacy: - td_->dialog_manager_->reload_dialog_info(source.dialog_photo().dialog_id, std::move(promise)); + case PhotoSizeSource::Type::DialogPhotoSmallLegacy: { + auto dialog_id = source.dialog_photo().dialog_id; + if (dialog_id.get_type() == DialogType::Channel) { + auto channel_id = dialog_id.get_channel_id(); + if (!channel_id.is_regular_channel()) { + td_->community_manager_->reload_community(CommunityId(channel_id.get()), std::move(promise), "reload_photo"); + break; + } + } + td_->dialog_manager_->reload_dialog_info(dialog_id, std::move(promise)); break; + } case PhotoSizeSource::Type::StickerSetThumbnail: case PhotoSizeSource::Type::StickerSetThumbnailLegacy: case PhotoSizeSource::Type::StickerSetThumbnailVersion: diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index d2929096a..8121c6bb4 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -960,6 +960,11 @@ void Td::init(Parameters parameters, Result<TdDb::OpenedDatabase> r_opened_datab void Td::process_binlog_events(TdDb::OpenedDatabase &&events) { VLOG(td_init) << "Send binlog events"; + // users and channels may contain links to communities, therefore must be inited after + for (auto &event : events.community_events) { + community_manager_->on_binlog_community_event(std::move(event)); + } + for (auto &event : events.user_events) { user_manager_->on_binlog_user_event(std::move(event)); } @@ -968,7 +973,7 @@ void Td::process_binlog_events(TdDb::OpenedDatabase &&events) { chat_manager_->on_binlog_channel_event(std::move(event)); } - // chats may contain links to channels, so should be inited after + // chats may contain links to channels, so must be inited after for (auto &event : events.chat_events) { chat_manager_->on_binlog_chat_event(std::move(event)); } diff --git a/td/telegram/TdDb.cpp b/td/telegram/TdDb.cpp index c49ad458b..d688763a7 100644 --- a/td/telegram/TdDb.cpp +++ b/td/telegram/TdDb.cpp @@ -85,6 +85,9 @@ Status init_binlog(Binlog &binlog, string path, BinlogKeyValue<Binlog> &binlog_p case LogEvent::HandlerType::SecretChatInfos: events.secret_chat_events.push_back(event.clone()); break; + case LogEvent::HandlerType::Communities: + events.community_events.push_back(event.clone()); + break; case LogEvent::HandlerType::WebPages: events.web_page_events.push_back(event.clone()); break; diff --git a/td/telegram/TdDb.h b/td/telegram/TdDb.h index 79a94f80a..f14ed1b97 100644 --- a/td/telegram/TdDb.h +++ b/td/telegram/TdDb.h @@ -70,6 +70,7 @@ class TdDb { vector<BinlogEvent> chat_events; vector<BinlogEvent> channel_events; vector<BinlogEvent> secret_chat_events; + vector<BinlogEvent> community_events; vector<BinlogEvent> web_page_events; vector<BinlogEvent> save_app_log_events; vector<BinlogEvent> to_account_manager; diff --git a/td/telegram/logevent/LogEvent.h b/td/telegram/logevent/LogEvent.h index ebb53adc3..0be5806d5 100644 --- a/td/telegram/logevent/LogEvent.h +++ b/td/telegram/logevent/LogEvent.h @@ -66,6 +66,7 @@ class LogEvent { Chats = 3, Channels = 4, SecretChatInfos = 5, + Communities = 6, WebPages = 0x10, SetPollAnswer = 0x20, StopPoll = 0x21, |
