2018-12-31 20:04:05 +01:00
|
|
|
//
|
2018-12-31 23:02:34 +01:00
|
|
|
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019
|
2018-12-31 20:04:05 +01:00
|
|
|
//
|
|
|
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
|
|
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
|
|
//
|
|
|
|
#include "td/telegram/TopDialogManager.h"
|
|
|
|
|
2018-12-12 16:38:20 +01:00
|
|
|
#include "td/telegram/AuthManager.h"
|
2018-12-31 20:04:05 +01:00
|
|
|
#include "td/telegram/ConfigShared.h"
|
|
|
|
#include "td/telegram/ContactsManager.h"
|
|
|
|
#include "td/telegram/DialogId.h"
|
|
|
|
#include "td/telegram/Global.h"
|
|
|
|
#include "td/telegram/logevent/LogEvent.h"
|
|
|
|
#include "td/telegram/MessagesManager.h"
|
|
|
|
#include "td/telegram/misc.h"
|
|
|
|
#include "td/telegram/net/NetQuery.h"
|
|
|
|
#include "td/telegram/net/NetQueryDispatcher.h"
|
|
|
|
#include "td/telegram/StateManager.h"
|
|
|
|
#include "td/telegram/Td.h"
|
|
|
|
|
|
|
|
#include "td/utils/misc.h"
|
|
|
|
#include "td/utils/port/Clocks.h"
|
|
|
|
#include "td/utils/ScopeGuard.h"
|
|
|
|
#include "td/utils/Slice.h"
|
|
|
|
#include "td/utils/Status.h"
|
|
|
|
#include "td/utils/tl_helpers.h"
|
|
|
|
|
|
|
|
#include "td/telegram/telegram_api.h"
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <cmath>
|
|
|
|
#include <iterator>
|
|
|
|
|
|
|
|
namespace td {
|
|
|
|
|
|
|
|
static CSlice top_dialog_category_name(TopDialogCategory category) {
|
|
|
|
switch (category) {
|
|
|
|
case TopDialogCategory::Correspondent:
|
|
|
|
return CSlice("correspondent");
|
|
|
|
case TopDialogCategory::BotPM:
|
|
|
|
return CSlice("bot_pm");
|
|
|
|
case TopDialogCategory::BotInline:
|
|
|
|
return CSlice("bot_inline");
|
|
|
|
case TopDialogCategory::Group:
|
|
|
|
return CSlice("group");
|
|
|
|
case TopDialogCategory::Channel:
|
|
|
|
return CSlice("channel");
|
|
|
|
case TopDialogCategory::Call:
|
|
|
|
return CSlice("call");
|
|
|
|
default:
|
|
|
|
UNREACHABLE();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static TopDialogCategory top_dialog_category_from_telegram_api(const telegram_api::TopPeerCategory &category) {
|
|
|
|
switch (category.get_id()) {
|
|
|
|
case telegram_api::topPeerCategoryCorrespondents::ID:
|
|
|
|
return TopDialogCategory::Correspondent;
|
|
|
|
case telegram_api::topPeerCategoryBotsPM::ID:
|
|
|
|
return TopDialogCategory::BotPM;
|
|
|
|
case telegram_api::topPeerCategoryBotsInline::ID:
|
|
|
|
return TopDialogCategory::BotInline;
|
|
|
|
case telegram_api::topPeerCategoryGroups::ID:
|
|
|
|
return TopDialogCategory::Group;
|
|
|
|
case telegram_api::topPeerCategoryChannels::ID:
|
|
|
|
return TopDialogCategory::Channel;
|
|
|
|
case telegram_api::topPeerCategoryPhoneCalls::ID:
|
|
|
|
return TopDialogCategory::Call;
|
|
|
|
default:
|
|
|
|
UNREACHABLE();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static tl_object_ptr<telegram_api::TopPeerCategory> top_dialog_category_as_telegram_api(TopDialogCategory category) {
|
|
|
|
switch (category) {
|
|
|
|
case TopDialogCategory::Correspondent:
|
|
|
|
return make_tl_object<telegram_api::topPeerCategoryCorrespondents>();
|
|
|
|
case TopDialogCategory::BotPM:
|
|
|
|
return make_tl_object<telegram_api::topPeerCategoryBotsPM>();
|
|
|
|
case TopDialogCategory::BotInline:
|
|
|
|
return make_tl_object<telegram_api::topPeerCategoryBotsInline>();
|
|
|
|
case TopDialogCategory::Group:
|
|
|
|
return make_tl_object<telegram_api::topPeerCategoryGroups>();
|
|
|
|
case TopDialogCategory::Channel:
|
|
|
|
return make_tl_object<telegram_api::topPeerCategoryChannels>();
|
|
|
|
case TopDialogCategory::Call:
|
|
|
|
return make_tl_object<telegram_api::topPeerCategoryPhoneCalls>();
|
|
|
|
default:
|
|
|
|
UNREACHABLE();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-26 21:51:00 +02:00
|
|
|
void TopDialogManager::update_is_enabled(bool is_enabled) {
|
|
|
|
if (set_is_enabled(is_enabled)) {
|
|
|
|
G()->td_db()->get_binlog_pmc()->set("top_peers_enabled", is_enabled ? "1" : "0");
|
|
|
|
send_toggle_top_peers(is_enabled);
|
|
|
|
|
|
|
|
loop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TopDialogManager::set_is_enabled(bool is_enabled) {
|
|
|
|
if (is_enabled_ == is_enabled) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
LOG(DEBUG) << "Change top chats is_enabled to " << is_enabled;
|
|
|
|
is_enabled_ = is_enabled;
|
|
|
|
init();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TopDialogManager::send_toggle_top_peers(bool is_enabled) {
|
|
|
|
if (have_toggle_top_peers_query_) {
|
|
|
|
have_pending_toggle_top_peers_query_ = true;
|
|
|
|
pending_toggle_top_peers_query_ = is_enabled;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
LOG(DEBUG) << "Send toggle top peers query to " << is_enabled;
|
|
|
|
have_toggle_top_peers_query_ = true;
|
|
|
|
toggle_top_peers_query_is_enabled_ = is_enabled;
|
|
|
|
auto net_query = G()->net_query_creator().create(create_storer(telegram_api::contacts_toggleTopPeers(is_enabled)));
|
|
|
|
G()->net_query_dispatcher().dispatch_with_callback(std::move(net_query), actor_shared(this, 2));
|
|
|
|
}
|
|
|
|
|
2018-12-31 20:04:05 +01:00
|
|
|
void TopDialogManager::on_dialog_used(TopDialogCategory category, DialogId dialog_id, int32 date) {
|
2018-06-26 21:51:00 +02:00
|
|
|
if (!is_active_ || !is_enabled_) {
|
2018-12-31 20:04:05 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto pos = static_cast<size_t>(category);
|
|
|
|
CHECK(pos < by_category_.size());
|
|
|
|
auto &top_dialogs = by_category_[pos];
|
|
|
|
|
|
|
|
top_dialogs.is_dirty = true;
|
|
|
|
auto it = std::find_if(top_dialogs.dialogs.begin(), top_dialogs.dialogs.end(),
|
|
|
|
[&](auto &top_dialog) { return top_dialog.dialog_id == dialog_id; });
|
|
|
|
if (it == top_dialogs.dialogs.end()) {
|
|
|
|
TopDialog top_dialog;
|
|
|
|
top_dialog.dialog_id = dialog_id;
|
|
|
|
top_dialogs.dialogs.push_back(top_dialog);
|
|
|
|
it = top_dialogs.dialogs.end() - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto delta = rating_add(date, top_dialogs.rating_timestamp);
|
|
|
|
it->rating += delta;
|
|
|
|
while (it != top_dialogs.dialogs.begin()) {
|
|
|
|
auto next = std::prev(it);
|
|
|
|
if (*next < *it) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
std::swap(*next, *it);
|
|
|
|
it = next;
|
|
|
|
}
|
|
|
|
|
|
|
|
LOG(INFO) << "Update " << top_dialog_category_name(category) << " rating of " << dialog_id << " by " << delta;
|
|
|
|
|
|
|
|
if (!first_unsync_change_) {
|
|
|
|
first_unsync_change_ = Timestamp::now_cached();
|
|
|
|
}
|
|
|
|
loop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void TopDialogManager::remove_dialog(TopDialogCategory category, DialogId dialog_id,
|
|
|
|
tl_object_ptr<telegram_api::InputPeer> input_peer) {
|
2018-06-26 21:51:00 +02:00
|
|
|
if (!is_active_ || !is_enabled_) {
|
2018-12-31 20:04:05 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto pos = static_cast<size_t>(category);
|
|
|
|
CHECK(pos < by_category_.size());
|
|
|
|
auto &top_dialogs = by_category_[pos];
|
|
|
|
|
|
|
|
LOG(INFO) << "Remove " << top_dialog_category_name(category) << " rating of " << dialog_id;
|
|
|
|
|
|
|
|
if (input_peer != nullptr) {
|
|
|
|
auto query =
|
|
|
|
telegram_api::contacts_resetTopPeerRating(top_dialog_category_as_telegram_api(category), std::move(input_peer));
|
|
|
|
auto net_query = G()->net_query_creator().create(create_storer(query));
|
|
|
|
G()->net_query_dispatcher().dispatch_with_callback(std::move(net_query), actor_shared(this, 1));
|
|
|
|
}
|
|
|
|
|
|
|
|
auto it = std::find_if(top_dialogs.dialogs.begin(), top_dialogs.dialogs.end(),
|
|
|
|
[&](auto &top_dialog) { return top_dialog.dialog_id == dialog_id; });
|
|
|
|
if (it == top_dialogs.dialogs.end()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
top_dialogs.is_dirty = true;
|
|
|
|
top_dialogs.dialogs.erase(it);
|
|
|
|
if (!first_unsync_change_) {
|
|
|
|
first_unsync_change_ = Timestamp::now_cached();
|
|
|
|
}
|
|
|
|
loop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void TopDialogManager::get_top_dialogs(TopDialogCategory category, size_t limit, Promise<vector<DialogId>> promise) {
|
|
|
|
if (!is_active_) {
|
|
|
|
promise.set_error(Status::Error(400, "Not supported without chat info database"));
|
|
|
|
return;
|
|
|
|
}
|
2018-06-26 21:51:00 +02:00
|
|
|
if (!is_enabled_) {
|
|
|
|
promise.set_error(Status::Error(400, "Top chats computation is disabled"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-12-31 20:04:05 +01:00
|
|
|
GetTopDialogsQuery query;
|
|
|
|
query.category = category;
|
|
|
|
query.limit = limit;
|
|
|
|
query.promise = std::move(promise);
|
|
|
|
pending_get_top_dialogs_.push_back(std::move(query));
|
|
|
|
loop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void TopDialogManager::update_rating_e_decay() {
|
|
|
|
if (!is_active_) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
rating_e_decay_ = G()->shared_config().get_option_integer("rating_e_decay", rating_e_decay_);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class T>
|
|
|
|
void parse(TopDialogManager::TopDialog &top_dialog, T &parser) {
|
|
|
|
using ::td::parse;
|
|
|
|
parse(top_dialog.dialog_id, parser);
|
|
|
|
parse(top_dialog.rating, parser);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class T>
|
|
|
|
void store(const TopDialogManager::TopDialog &top_dialog, T &storer) {
|
|
|
|
using ::td::store;
|
|
|
|
store(top_dialog.dialog_id, storer);
|
|
|
|
store(top_dialog.rating, storer);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class T>
|
|
|
|
void parse(TopDialogManager::TopDialogs &top_dialogs, T &parser) {
|
|
|
|
using ::td::parse;
|
|
|
|
parse(top_dialogs.rating_timestamp, parser);
|
|
|
|
parse(top_dialogs.dialogs, parser);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class T>
|
|
|
|
void store(const TopDialogManager::TopDialogs &top_dialogs, T &storer) {
|
|
|
|
using ::td::store;
|
|
|
|
store(top_dialogs.rating_timestamp, storer);
|
|
|
|
store(top_dialogs.dialogs, storer);
|
|
|
|
}
|
|
|
|
|
|
|
|
double TopDialogManager::rating_add(double now, double rating_timestamp) const {
|
|
|
|
return std::exp((now - rating_timestamp) / rating_e_decay_);
|
|
|
|
}
|
|
|
|
|
|
|
|
double TopDialogManager::current_rating_add(double rating_timestamp) const {
|
|
|
|
return rating_add(G()->server_time_cached(), rating_timestamp);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TopDialogManager::normalize_rating() {
|
|
|
|
for (auto &top_dialogs : by_category_) {
|
|
|
|
auto div_by = current_rating_add(top_dialogs.rating_timestamp);
|
|
|
|
top_dialogs.rating_timestamp = G()->server_time_cached();
|
|
|
|
for (auto &dialog : top_dialogs.dialogs) {
|
|
|
|
dialog.rating /= div_by;
|
|
|
|
}
|
|
|
|
top_dialogs.is_dirty = true;
|
|
|
|
}
|
|
|
|
db_sync_state_ = SyncState::None;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TopDialogManager::do_get_top_dialogs(GetTopDialogsQuery &&query) {
|
|
|
|
auto pos = static_cast<size_t>(query.category);
|
|
|
|
CHECK(pos < by_category_.size());
|
|
|
|
auto &top_dialogs = by_category_[pos];
|
|
|
|
|
2018-03-08 16:21:40 +01:00
|
|
|
auto limit = std::min({query.limit, MAX_TOP_DIALOGS_LIMIT, top_dialogs.dialogs.size()});
|
2018-12-31 20:04:05 +01:00
|
|
|
|
|
|
|
vector<DialogId> dialog_ids = transform(top_dialogs.dialogs, [](const auto &x) { return x.dialog_id; });
|
|
|
|
|
|
|
|
auto promise = PromiseCreator::lambda([query = std::move(query), dialog_ids, limit](Result<Unit>) mutable {
|
|
|
|
vector<DialogId> result;
|
|
|
|
result.reserve(limit);
|
|
|
|
for (auto dialog_id : dialog_ids) {
|
2018-03-08 16:21:40 +01:00
|
|
|
if (dialog_id.get_type() == DialogType::User) {
|
|
|
|
auto user_id = dialog_id.get_user_id();
|
|
|
|
if (G()->td().get_actor_unsafe()->contacts_manager_->is_user_deleted(user_id)) {
|
|
|
|
LOG(INFO) << "Skip deleted " << user_id;
|
|
|
|
continue;
|
|
|
|
}
|
2018-10-08 16:21:56 +02:00
|
|
|
if (G()->td().get_actor_unsafe()->contacts_manager_->get_my_id() == user_id) {
|
2018-03-08 16:21:40 +01:00
|
|
|
LOG(INFO) << "Skip self " << user_id;
|
|
|
|
continue;
|
|
|
|
}
|
2018-10-22 15:47:41 +02:00
|
|
|
if (query.category == TopDialogCategory::BotInline || query.category == TopDialogCategory::BotPM) {
|
|
|
|
auto r_bot_info = G()->td().get_actor_unsafe()->contacts_manager_->get_bot_data(user_id);
|
|
|
|
if (r_bot_info.is_error()) {
|
|
|
|
LOG(INFO) << "Skip not a bot " << user_id;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (query.category == TopDialogCategory::BotInline &&
|
|
|
|
(r_bot_info.ok().username.empty() || !r_bot_info.ok().is_inline)) {
|
|
|
|
LOG(INFO) << "Skip not inline bot " << user_id;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
2018-12-31 20:04:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
result.push_back(dialog_id);
|
|
|
|
if (result.size() == limit) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
query.promise.set_value(std::move(result));
|
|
|
|
});
|
|
|
|
send_closure(G()->messages_manager(), &MessagesManager::load_dialogs, std::move(dialog_ids), std::move(promise));
|
|
|
|
}
|
|
|
|
|
|
|
|
void TopDialogManager::do_get_top_peers() {
|
|
|
|
LOG(INFO) << "Send get top peers request";
|
|
|
|
using telegram_api::contacts_getTopPeers;
|
|
|
|
|
|
|
|
std::vector<uint32> ids;
|
|
|
|
for (auto &category : by_category_) {
|
|
|
|
for (auto &top_dialog : category.dialogs) {
|
|
|
|
auto dialog_id = top_dialog.dialog_id;
|
|
|
|
switch (dialog_id.get_type()) {
|
|
|
|
case DialogType::Channel:
|
|
|
|
ids.push_back(dialog_id.get_channel_id().get());
|
|
|
|
break;
|
|
|
|
case DialogType::User:
|
|
|
|
ids.push_back(dialog_id.get_user_id().get());
|
|
|
|
break;
|
|
|
|
case DialogType::Chat:
|
|
|
|
ids.push_back(dialog_id.get_chat_id().get());
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int32 hash = get_vector_hash(ids);
|
|
|
|
|
|
|
|
int32 flags = contacts_getTopPeers::CORRESPONDENTS_MASK | contacts_getTopPeers::BOTS_PM_MASK |
|
|
|
|
contacts_getTopPeers::BOTS_INLINE_MASK | contacts_getTopPeers::GROUPS_MASK |
|
|
|
|
contacts_getTopPeers::CHANNELS_MASK | contacts_getTopPeers::PHONE_CALLS_MASK;
|
|
|
|
|
|
|
|
contacts_getTopPeers query{
|
|
|
|
flags, true /*correspondents*/, true /*bot_pm*/, true /*bot_inline */, true /*phone_calls*/,
|
|
|
|
true /*groups*/, true /*channels*/, 0 /*offset*/, 100 /*limit*/, hash};
|
|
|
|
auto net_query = G()->net_query_creator().create(create_storer(query));
|
|
|
|
G()->net_query_dispatcher().dispatch_with_callback(std::move(net_query), actor_shared(this));
|
|
|
|
}
|
|
|
|
|
|
|
|
void TopDialogManager::on_result(NetQueryPtr net_query) {
|
2018-06-26 21:51:00 +02:00
|
|
|
auto query_type = get_link_token();
|
|
|
|
if (query_type == 2) { // toggleTopPeers
|
|
|
|
CHECK(have_toggle_top_peers_query_);
|
|
|
|
have_toggle_top_peers_query_ = false;
|
|
|
|
|
|
|
|
if (have_pending_toggle_top_peers_query_) {
|
|
|
|
have_pending_toggle_top_peers_query_ = false;
|
|
|
|
if (pending_toggle_top_peers_query_ != toggle_top_peers_query_is_enabled_) {
|
|
|
|
send_toggle_top_peers(pending_toggle_top_peers_query_);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto r_result = fetch_result<telegram_api::contacts_toggleTopPeers>(std::move(net_query));
|
|
|
|
if (r_result.is_ok()) {
|
|
|
|
// everything is synchronized
|
|
|
|
G()->td_db()->get_binlog_pmc()->erase("top_peers_enabled");
|
|
|
|
} else {
|
|
|
|
// let's resend the query forever
|
|
|
|
if (!G()->close_flag()) {
|
|
|
|
send_toggle_top_peers(toggle_top_peers_query_is_enabled_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (query_type == 1) { // resetTopPeerRating
|
|
|
|
// ignore result
|
2018-12-31 20:04:05 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
SCOPE_EXIT {
|
|
|
|
loop();
|
|
|
|
};
|
2018-06-26 21:51:00 +02:00
|
|
|
|
2018-12-31 20:04:05 +01:00
|
|
|
normalize_rating(); // once a day too
|
|
|
|
|
|
|
|
auto r_top_peers = fetch_result<telegram_api::contacts_getTopPeers>(std::move(net_query));
|
|
|
|
if (r_top_peers.is_error()) {
|
2018-06-26 21:51:00 +02:00
|
|
|
last_server_sync_ = Timestamp::in(SERVER_SYNC_RESEND_DELAY - SERVER_SYNC_DELAY);
|
2018-06-25 23:10:53 +02:00
|
|
|
return;
|
|
|
|
}
|
2018-12-31 20:04:05 +01:00
|
|
|
|
2018-06-26 21:51:00 +02:00
|
|
|
last_server_sync_ = Timestamp::now();
|
|
|
|
server_sync_state_ = SyncState::Ok;
|
|
|
|
SCOPE_EXIT {
|
|
|
|
G()->td_db()->get_binlog_pmc()->set("top_dialogs_ts", to_string(static_cast<uint32>(Clocks::system())));
|
|
|
|
};
|
2018-12-31 20:04:05 +01:00
|
|
|
|
2018-06-26 21:51:00 +02:00
|
|
|
auto top_peers_parent = r_top_peers.move_as_ok();
|
|
|
|
LOG(DEBUG) << "contacts_getTopPeers returned " << to_string(top_peers_parent);
|
|
|
|
switch (top_peers_parent->get_id()) {
|
|
|
|
case telegram_api::contacts_topPeersNotModified::ID:
|
|
|
|
// nothing to do
|
|
|
|
return;
|
|
|
|
case telegram_api::contacts_topPeersDisabled::ID:
|
|
|
|
G()->shared_config().set_option_boolean("disable_top_chats", true);
|
|
|
|
set_is_enabled(false); // apply immediately
|
|
|
|
return;
|
|
|
|
case telegram_api::contacts_topPeers::ID: {
|
|
|
|
G()->shared_config().set_option_empty("disable_top_chats");
|
|
|
|
set_is_enabled(true); // apply immediately
|
|
|
|
auto top_peers = move_tl_object_as<telegram_api::contacts_topPeers>(std::move(top_peers_parent));
|
|
|
|
|
|
|
|
send_closure(G()->contacts_manager(), &ContactsManager::on_get_users, std::move(top_peers->users_));
|
|
|
|
send_closure(G()->contacts_manager(), &ContactsManager::on_get_chats, std::move(top_peers->chats_));
|
|
|
|
for (auto &category : top_peers->categories_) {
|
|
|
|
auto dialog_category = top_dialog_category_from_telegram_api(*category->category_);
|
|
|
|
auto pos = static_cast<size_t>(dialog_category);
|
|
|
|
CHECK(pos < by_category_.size());
|
|
|
|
auto &top_dialogs = by_category_[pos];
|
|
|
|
|
|
|
|
top_dialogs.is_dirty = true;
|
|
|
|
top_dialogs.dialogs.clear();
|
|
|
|
for (auto &top_peer : category->peers_) {
|
|
|
|
TopDialog top_dialog;
|
|
|
|
top_dialog.dialog_id = DialogId(top_peer->peer_);
|
|
|
|
top_dialog.rating = top_peer->rating_;
|
|
|
|
top_dialogs.dialogs.push_back(std::move(top_dialog));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
db_sync_state_ = SyncState::None;
|
|
|
|
break;
|
2018-12-31 20:04:05 +01:00
|
|
|
}
|
2018-06-26 21:51:00 +02:00
|
|
|
default:
|
|
|
|
UNREACHABLE();
|
2018-12-31 20:04:05 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TopDialogManager::do_save_top_dialogs() {
|
|
|
|
LOG(INFO) << "Save top chats";
|
|
|
|
for (size_t top_dialog_category_i = 0; top_dialog_category_i < by_category_.size(); top_dialog_category_i++) {
|
|
|
|
auto top_dialog_category = TopDialogCategory(top_dialog_category_i);
|
|
|
|
auto key = PSTRING() << "top_dialogs#" << top_dialog_category_name(top_dialog_category);
|
|
|
|
|
|
|
|
auto &top_dialogs = by_category_[top_dialog_category_i];
|
|
|
|
if (!top_dialogs.is_dirty) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
top_dialogs.is_dirty = false;
|
|
|
|
|
|
|
|
G()->td_db()->get_binlog_pmc()->set(key, log_event_store(top_dialogs).as_slice().str());
|
|
|
|
}
|
|
|
|
db_sync_state_ = SyncState::Ok;
|
|
|
|
first_unsync_change_ = Timestamp();
|
|
|
|
}
|
|
|
|
|
|
|
|
void TopDialogManager::start_up() {
|
2018-12-12 16:38:20 +01:00
|
|
|
is_active_ = G()->parameters().use_chat_info_db && !G()->td().get_actor_unsafe()->auth_manager_->is_bot();
|
2018-06-26 21:51:00 +02:00
|
|
|
is_enabled_ = !G()->shared_config().get_option_boolean("disable_top_chats");
|
|
|
|
update_rating_e_decay();
|
|
|
|
|
|
|
|
string need_update_top_peers = G()->td_db()->get_binlog_pmc()->get("top_peers_enabled");
|
|
|
|
if (!need_update_top_peers.empty()) {
|
|
|
|
send_toggle_top_peers(need_update_top_peers[0] == '1');
|
|
|
|
}
|
|
|
|
|
|
|
|
init();
|
|
|
|
loop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void TopDialogManager::init() {
|
|
|
|
was_first_sync_ = false;
|
|
|
|
first_unsync_change_ = Timestamp();
|
|
|
|
server_sync_state_ = SyncState::None;
|
|
|
|
last_server_sync_ = Timestamp();
|
|
|
|
CHECK(pending_get_top_dialogs_.empty());
|
|
|
|
|
|
|
|
LOG(DEBUG) << "Init is enabled: " << is_enabled_;
|
|
|
|
if (!is_active_) {
|
2018-12-31 20:04:05 +01:00
|
|
|
G()->td_db()->get_binlog_pmc()->erase_by_prefix("top_dialogs");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto di_top_dialogs_ts = G()->td_db()->get_binlog_pmc()->get("top_dialogs_ts");
|
|
|
|
if (!di_top_dialogs_ts.empty()) {
|
|
|
|
last_server_sync_ = Timestamp::in(to_integer<uint32>(di_top_dialogs_ts) - Clocks::system());
|
|
|
|
if (last_server_sync_.is_in_past()) {
|
|
|
|
server_sync_state_ = SyncState::Ok;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-26 21:51:00 +02:00
|
|
|
if (is_enabled_) {
|
|
|
|
for (size_t top_dialog_category_i = 0; top_dialog_category_i < by_category_.size(); top_dialog_category_i++) {
|
|
|
|
auto top_dialog_category = TopDialogCategory(top_dialog_category_i);
|
|
|
|
auto key = PSTRING() << "top_dialogs#" << top_dialog_category_name(top_dialog_category);
|
|
|
|
auto value = G()->td_db()->get_binlog_pmc()->get(key);
|
2018-12-31 20:04:05 +01:00
|
|
|
|
2018-06-26 21:51:00 +02:00
|
|
|
auto &top_dialogs = by_category_[top_dialog_category_i];
|
|
|
|
top_dialogs.is_dirty = false;
|
|
|
|
if (value.empty()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
log_event_parse(top_dialogs, value).ensure();
|
|
|
|
}
|
|
|
|
normalize_rating();
|
|
|
|
} else {
|
|
|
|
G()->td_db()->get_binlog_pmc()->erase_by_prefix("top_dialogs#");
|
|
|
|
for (auto &top_dialogs : by_category_) {
|
|
|
|
top_dialogs.is_dirty = false;
|
|
|
|
top_dialogs.rating_timestamp = 0;
|
|
|
|
top_dialogs.dialogs.clear();
|
2018-12-31 20:04:05 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
db_sync_state_ = SyncState::Ok;
|
|
|
|
|
|
|
|
send_closure(G()->state_manager(), &StateManager::wait_first_sync,
|
|
|
|
PromiseCreator::event(self_closure(this, &TopDialogManager::on_first_sync)));
|
|
|
|
}
|
|
|
|
|
|
|
|
void TopDialogManager::on_first_sync() {
|
|
|
|
was_first_sync_ = true;
|
2018-12-12 18:02:50 +01:00
|
|
|
if (!G()->close_flag() && G()->td().get_actor_unsafe()->auth_manager_->is_bot()) {
|
2018-12-12 16:38:20 +01:00
|
|
|
is_active_ = false;
|
|
|
|
init();
|
|
|
|
}
|
2018-12-31 20:04:05 +01:00
|
|
|
loop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void TopDialogManager::loop() {
|
2018-06-27 20:26:52 +02:00
|
|
|
if (!is_active_ || G()->close_flag()) {
|
2018-12-31 20:04:05 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!pending_get_top_dialogs_.empty()) {
|
|
|
|
for (auto &query : pending_get_top_dialogs_) {
|
|
|
|
do_get_top_dialogs(std::move(query));
|
|
|
|
}
|
|
|
|
pending_get_top_dialogs_.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
// server sync
|
|
|
|
Timestamp server_sync_timeout;
|
|
|
|
if (server_sync_state_ == SyncState::Ok) {
|
|
|
|
server_sync_timeout = Timestamp::at(last_server_sync_.at() + SERVER_SYNC_DELAY);
|
|
|
|
if (server_sync_timeout.is_in_past()) {
|
|
|
|
server_sync_state_ = SyncState::None;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-08 16:21:40 +01:00
|
|
|
Timestamp wakeup_timeout;
|
2018-12-31 20:04:05 +01:00
|
|
|
if (server_sync_state_ == SyncState::Ok) {
|
|
|
|
wakeup_timeout.relax(server_sync_timeout);
|
|
|
|
} else if (server_sync_state_ == SyncState::None && was_first_sync_) {
|
|
|
|
server_sync_state_ = SyncState::Pending;
|
|
|
|
do_get_top_peers();
|
|
|
|
}
|
|
|
|
|
2018-06-26 21:51:00 +02:00
|
|
|
if (is_enabled_) {
|
2018-12-13 23:48:36 +01:00
|
|
|
// database sync
|
2018-06-26 21:51:00 +02:00
|
|
|
Timestamp db_sync_timeout;
|
|
|
|
if (db_sync_state_ == SyncState::Ok) {
|
|
|
|
if (first_unsync_change_) {
|
|
|
|
db_sync_timeout = Timestamp::at(first_unsync_change_.at() + DB_SYNC_DELAY);
|
|
|
|
if (db_sync_timeout.is_in_past()) {
|
|
|
|
db_sync_state_ = SyncState::None;
|
|
|
|
}
|
2018-12-31 20:04:05 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-26 21:51:00 +02:00
|
|
|
if (db_sync_state_ == SyncState::Ok) {
|
|
|
|
wakeup_timeout.relax(db_sync_timeout);
|
|
|
|
} else if (db_sync_state_ == SyncState::None) {
|
|
|
|
if (server_sync_state_ == SyncState::Ok) {
|
|
|
|
do_save_top_dialogs();
|
|
|
|
}
|
2018-12-31 20:04:05 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wakeup_timeout) {
|
|
|
|
LOG(INFO) << "Wakeup in: " << wakeup_timeout.in();
|
|
|
|
set_timeout_at(wakeup_timeout.at());
|
|
|
|
} else {
|
|
|
|
LOG(INFO) << "Wakeup: never";
|
|
|
|
cancel_timeout();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace td
|