aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--td/generate/scheme/td_api.tl12
-rw-r--r--td/telegram/ChannelId.h6
-rw-r--r--td/telegram/ChatManager.h1
-rw-r--r--td/telegram/CommunityManager.cpp584
-rw-r--r--td/telegram/CommunityManager.h112
-rw-r--r--td/telegram/FileReferenceManager.cpp15
-rw-r--r--td/telegram/Td.cpp7
-rw-r--r--td/telegram/TdDb.cpp3
-rw-r--r--td/telegram/TdDb.h1
-rw-r--r--td/telegram/logevent/LogEvent.h1
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,