top_peers.cpp 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "data/components/top_peers.h"
  8. #include "api/api_hash.h"
  9. #include "apiwrap.h"
  10. #include "data/data_peer.h"
  11. #include "data/data_session.h"
  12. #include "data/data_user.h"
  13. #include "main/main_session.h"
  14. #include "mtproto/mtproto_config.h"
  15. #include "storage/serialize_common.h"
  16. #include "storage/serialize_peer.h"
  17. #include "storage/storage_account.h"
  18. namespace Data {
  19. namespace {
  20. constexpr auto kLimit = 64;
  21. constexpr auto kRequestTimeLimit = 10 * crl::time(1000);
  22. [[nodiscard]] float64 RatingDelta(TimeId now, TimeId was, int decay) {
  23. return std::exp((now - was) * 1. / decay);
  24. }
  25. [[nodiscard]] quint64 SerializeRating(float64 rating) {
  26. return quint64(
  27. base::SafeRound(std::clamp(rating, 0., 1'000'000.) * 1'000'000.));
  28. }
  29. [[nodiscard]] float64 DeserializeRating(quint64 rating) {
  30. return std::clamp(
  31. rating,
  32. quint64(),
  33. quint64(1'000'000'000'000ULL)
  34. ) / 1'000'000.;
  35. }
  36. [[nodiscard]] MTPTopPeerCategory TypeToCategory(TopPeerType type) {
  37. switch (type) {
  38. case TopPeerType::Chat: return MTP_topPeerCategoryCorrespondents();
  39. case TopPeerType::BotApp: return MTP_topPeerCategoryBotsApp();
  40. }
  41. Unexpected("Type in TypeToCategory.");
  42. }
  43. [[nodiscard]] auto TypeToGetFlags(TopPeerType type) {
  44. using Flag = MTPcontacts_GetTopPeers::Flag;
  45. switch (type) {
  46. case TopPeerType::Chat: return Flag::f_correspondents;
  47. case TopPeerType::BotApp: return Flag::f_bots_app;
  48. }
  49. Unexpected("Type in TypeToGetFlags.");
  50. }
  51. } // namespace
  52. TopPeers::TopPeers(not_null<Main::Session*> session, TopPeerType type)
  53. : _session(session)
  54. , _type(type) {
  55. if (_type == TopPeerType::Chat) {
  56. loadAfterChats();
  57. }
  58. }
  59. void TopPeers::loadAfterChats() {
  60. using namespace rpl::mappers;
  61. crl::on_main(_session, [=] {
  62. _session->data().chatsListLoadedEvents(
  63. ) | rpl::filter(_1 == nullptr) | rpl::start_with_next([=] {
  64. crl::on_main(_session, [=] {
  65. request();
  66. });
  67. }, _session->lifetime());
  68. });
  69. }
  70. TopPeers::~TopPeers() = default;
  71. std::vector<not_null<PeerData*>> TopPeers::list() const {
  72. _session->local().readSearchSuggestions();
  73. return _list
  74. | ranges::view::transform(&TopPeer::peer)
  75. | ranges::to_vector;
  76. }
  77. bool TopPeers::disabled() const {
  78. _session->local().readSearchSuggestions();
  79. return _disabled;
  80. }
  81. rpl::producer<> TopPeers::updates() const {
  82. return _updates.events();
  83. }
  84. void TopPeers::remove(not_null<PeerData*> peer) {
  85. const auto i = ranges::find(_list, peer, &TopPeer::peer);
  86. if (i != end(_list)) {
  87. _list.erase(i);
  88. updated();
  89. }
  90. _requestId = _session->api().request(MTPcontacts_ResetTopPeerRating(
  91. TypeToCategory(_type),
  92. peer->input
  93. )).send();
  94. }
  95. void TopPeers::increment(not_null<PeerData*> peer, TimeId date) {
  96. _session->local().readSearchSuggestions();
  97. if (_disabled || date <= _lastReceivedDate) {
  98. return;
  99. }
  100. if (const auto user = peer->asUser(); user && !user->isBot()) {
  101. auto changed = false;
  102. auto i = ranges::find(_list, peer, &TopPeer::peer);
  103. if (i == end(_list)) {
  104. _list.push_back({ .peer = peer });
  105. i = end(_list) - 1;
  106. changed = true;
  107. }
  108. const auto &config = peer->session().mtp().config();
  109. const auto decay = config.values().ratingDecay;
  110. i->rating += RatingDelta(date, _lastReceivedDate, decay);
  111. for (; i != begin(_list); --i) {
  112. if (i->rating >= (i - 1)->rating) {
  113. changed = true;
  114. std::swap(*i, *(i - 1));
  115. } else {
  116. break;
  117. }
  118. }
  119. if (changed) {
  120. updated();
  121. } else {
  122. _session->local().writeSearchSuggestionsDelayed();
  123. }
  124. }
  125. }
  126. void TopPeers::reload() {
  127. if (_requestId
  128. || (_lastReceived
  129. && _lastReceived + kRequestTimeLimit > crl::now())) {
  130. return;
  131. }
  132. request();
  133. }
  134. void TopPeers::toggleDisabled(bool disabled) {
  135. _session->local().readSearchSuggestions();
  136. if (disabled) {
  137. if (!_disabled || !_list.empty()) {
  138. _disabled = true;
  139. _list.clear();
  140. updated();
  141. }
  142. } else if (_disabled) {
  143. _disabled = false;
  144. updated();
  145. }
  146. _session->api().request(MTPcontacts_ToggleTopPeers(
  147. MTP_bool(!disabled)
  148. )).done([=] {
  149. if (!_disabled) {
  150. request();
  151. }
  152. }).send();
  153. }
  154. void TopPeers::request() {
  155. if (_requestId) {
  156. return;
  157. }
  158. _requestId = _session->api().request(MTPcontacts_GetTopPeers(
  159. MTP_flags(TypeToGetFlags(_type)),
  160. MTP_int(0),
  161. MTP_int(kLimit),
  162. MTP_long(countHash())
  163. )).done([=](
  164. const MTPcontacts_TopPeers &result,
  165. const MTP::Response &response) {
  166. _lastReceivedDate = TimeId(response.outerMsgId >> 32);
  167. _lastReceived = crl::now();
  168. _requestId = 0;
  169. result.match([&](const MTPDcontacts_topPeers &data) {
  170. _disabled = false;
  171. const auto owner = &_session->data();
  172. owner->processUsers(data.vusers());
  173. owner->processChats(data.vchats());
  174. for (const auto &category : data.vcategories().v) {
  175. const auto &data = category.data();
  176. const auto cons = (_type == TopPeerType::Chat)
  177. ? mtpc_topPeerCategoryCorrespondents
  178. : mtpc_topPeerCategoryBotsApp;
  179. if (data.vcategory().type() != cons) {
  180. LOG(("API Error: Unexpected top peer category."));
  181. continue;
  182. }
  183. _list = ranges::views::all(
  184. data.vpeers().v
  185. ) | ranges::views::transform([&](
  186. const MTPTopPeer &top) {
  187. return TopPeer{
  188. owner->peer(peerFromMTP(top.data().vpeer())),
  189. top.data().vrating().v,
  190. };
  191. }) | ranges::to_vector;
  192. }
  193. updated();
  194. }, [&](const MTPDcontacts_topPeersDisabled &) {
  195. if (!_disabled) {
  196. _list.clear();
  197. _disabled = true;
  198. updated();
  199. }
  200. }, [](const MTPDcontacts_topPeersNotModified &) {
  201. });
  202. }).fail([=] {
  203. _lastReceived = crl::now();
  204. _requestId = 0;
  205. }).send();
  206. }
  207. uint64 TopPeers::countHash() const {
  208. using namespace Api;
  209. auto hash = HashInit();
  210. for (const auto &top : _list | ranges::views::take(kLimit)) {
  211. HashUpdate(hash, peerToUser(top.peer->id).bare);
  212. }
  213. return HashFinalize(hash);
  214. }
  215. void TopPeers::updated() {
  216. _updates.fire({});
  217. _session->local().writeSearchSuggestionsDelayed();
  218. }
  219. QByteArray TopPeers::serialize() const {
  220. _session->local().readSearchSuggestions();
  221. if (!_disabled && _list.empty()) {
  222. return {};
  223. }
  224. auto size = 3 * sizeof(quint32); // AppVersion, disabled, count
  225. const auto count = std::min(int(_list.size()), kLimit);
  226. auto &&list = _list | ranges::views::take(count);
  227. for (const auto &top : list) {
  228. size += Serialize::peerSize(top.peer) + sizeof(quint64);
  229. }
  230. auto stream = Serialize::ByteArrayWriter(size);
  231. stream
  232. << quint32(AppVersion)
  233. << quint32(_disabled ? 1 : 0)
  234. << quint32(count);
  235. for (const auto &top : list) {
  236. Serialize::writePeer(stream, top.peer);
  237. stream << SerializeRating(top.rating);
  238. }
  239. return std::move(stream).result();
  240. }
  241. void TopPeers::applyLocal(QByteArray serialized) {
  242. if (_lastReceived) {
  243. DEBUG_LOG(("Suggestions: Skipping TopPeers local, got already."));
  244. return;
  245. }
  246. _list.clear();
  247. _disabled = false;
  248. if (serialized.isEmpty()) {
  249. DEBUG_LOG(("Suggestions: Bad TopPeers local, empty."));
  250. return;
  251. }
  252. auto stream = Serialize::ByteArrayReader(serialized);
  253. auto streamAppVersion = quint32();
  254. auto disabled = quint32();
  255. auto count = quint32();
  256. stream >> streamAppVersion >> disabled >> count;
  257. if (!stream.ok()) {
  258. DEBUG_LOG(("Suggestions: Bad TopPeers local, not ok."));
  259. return;
  260. }
  261. DEBUG_LOG(("Suggestions: "
  262. "Start TopPeers read, count: %1, version: %2, disabled: %3."
  263. ).arg(count
  264. ).arg(streamAppVersion
  265. ).arg(disabled));
  266. _list.reserve(count);
  267. for (auto i = 0; i != int(count); ++i) {
  268. auto rating = quint64();
  269. const auto streamPosition = stream.underlying().device()->pos();
  270. const auto peer = Serialize::readPeer(
  271. _session,
  272. streamAppVersion,
  273. stream);
  274. stream >> rating;
  275. if (stream.ok() && peer) {
  276. _list.push_back({
  277. .peer = peer,
  278. .rating = DeserializeRating(rating),
  279. });
  280. } else {
  281. DEBUG_LOG(("Suggestions: "
  282. "Failed TopPeers reading %1 / %2.").arg(i + 1).arg(count));
  283. DEBUG_LOG(("Failed bytes: %1.").arg(
  284. QString::fromUtf8(serialized.mid(streamPosition).toHex())));
  285. _list.clear();
  286. return;
  287. }
  288. }
  289. _disabled = (disabled == 1);
  290. DEBUG_LOG(
  291. ("Suggestions: TopPeers read OK, count: %1").arg(_list.size()));
  292. }
  293. } // namespace Data