data_replies_list.cpp 28 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045
  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/data_replies_list.h"
  8. #include "history/history.h"
  9. #include "history/history_item.h"
  10. #include "history/history_item_helpers.h"
  11. #include "main/main_session.h"
  12. #include "data/data_histories.h"
  13. #include "data/data_session.h"
  14. #include "data/data_changes.h"
  15. #include "data/data_channel.h"
  16. #include "data/data_messages.h"
  17. #include "data/data_forum.h"
  18. #include "data/data_forum_topic.h"
  19. #include "window/notifications_manager.h"
  20. #include "core/application.h"
  21. #include "lang/lang_keys.h"
  22. #include "apiwrap.h"
  23. namespace Data {
  24. namespace {
  25. constexpr auto kMessagesPerPage = 50;
  26. constexpr auto kReadRequestTimeout = 3 * crl::time(1000);
  27. constexpr auto kMaxMessagesToDeleteMyTopic = 10;
  28. [[nodiscard]] HistoryItem *GenerateDivider(
  29. not_null<History*> history,
  30. TimeId date,
  31. const QString &text) {
  32. return history->makeMessage({
  33. .id = history->nextNonHistoryEntryId(),
  34. .flags = MessageFlag::FakeHistoryItem,
  35. .date = date,
  36. }, PreparedServiceText{ { .text = text } });
  37. }
  38. [[nodiscard]] bool IsCreating(not_null<History*> history, MsgId rootId) {
  39. if (const auto forum = history->asForum()) {
  40. return forum->creating(rootId);
  41. }
  42. return false;
  43. }
  44. } // namespace
  45. struct RepliesList::Viewer {
  46. MessagesSlice slice;
  47. MsgId around = 0;
  48. int limitBefore = 0;
  49. int limitAfter = 0;
  50. int injectedForRoot = 0;
  51. base::has_weak_ptr guard;
  52. bool scheduled = false;
  53. };
  54. RepliesList::RepliesList(
  55. not_null<History*> history,
  56. MsgId rootId,
  57. ForumTopic *owningTopic)
  58. : _history(history)
  59. , _owningTopic(owningTopic)
  60. , _rootId(rootId)
  61. , _creating(IsCreating(history, rootId))
  62. , _readRequestTimer([=] { sendReadTillRequest(); }) {
  63. if (_owningTopic) {
  64. _owningTopic->destroyed(
  65. ) | rpl::start_with_next([=] {
  66. _owningTopic = nullptr;
  67. subscribeToUpdates();
  68. }, _lifetime);
  69. } else {
  70. subscribeToUpdates();
  71. }
  72. }
  73. RepliesList::~RepliesList() {
  74. histories().cancelRequest(base::take(_beforeId));
  75. histories().cancelRequest(base::take(_afterId));
  76. if (_readRequestTimer.isActive()) {
  77. sendReadTillRequest();
  78. }
  79. if (_divider) {
  80. _divider->destroy();
  81. }
  82. }
  83. void RepliesList::subscribeToUpdates() {
  84. _history->owner().repliesReadTillUpdates(
  85. ) | rpl::filter([=](const RepliesReadTillUpdate &update) {
  86. return (update.id.msg == _rootId)
  87. && (update.id.peer == _history->peer->id);
  88. }) | rpl::start_with_next([=](const RepliesReadTillUpdate &update) {
  89. apply(update);
  90. }, _lifetime);
  91. _history->session().changes().messageUpdates(
  92. MessageUpdate::Flag::NewAdded
  93. | MessageUpdate::Flag::NewMaybeAdded
  94. | MessageUpdate::Flag::ReplyToTopAdded
  95. | MessageUpdate::Flag::Destroyed
  96. ) | rpl::start_with_next([=](const MessageUpdate &update) {
  97. apply(update);
  98. }, _lifetime);
  99. _history->session().changes().topicUpdates(
  100. TopicUpdate::Flag::Creator
  101. ) | rpl::start_with_next([=](const TopicUpdate &update) {
  102. apply(update);
  103. }, _lifetime);
  104. _history->owner().channelDifferenceTooLong(
  105. ) | rpl::start_with_next([=](not_null<ChannelData*> channel) {
  106. if (channel == _history->peer) {
  107. applyDifferenceTooLong();
  108. }
  109. }, _lifetime);
  110. }
  111. void RepliesList::apply(const RepliesReadTillUpdate &update) {
  112. if (update.out) {
  113. setOutboxReadTill(update.readTillId);
  114. } else if (update.readTillId >= _inboxReadTillId) {
  115. setInboxReadTill(
  116. update.readTillId,
  117. computeUnreadCountLocally(update.readTillId));
  118. }
  119. }
  120. void RepliesList::apply(const MessageUpdate &update) {
  121. if (applyUpdate(update)) {
  122. _instantChanges.fire({});
  123. }
  124. }
  125. void RepliesList::apply(const TopicUpdate &update) {
  126. if (update.topic->history() == _history
  127. && update.topic->rootId() == _rootId) {
  128. if (update.flags & TopicUpdate::Flag::Creator) {
  129. applyTopicCreator(update.topic->creatorId());
  130. }
  131. }
  132. }
  133. void RepliesList::applyTopicCreator(PeerId creatorId) {
  134. const auto owner = &_history->owner();
  135. const auto peerId = _history->peer->id;
  136. for (const auto &id : _list) {
  137. if (const auto item = owner->message(peerId, id)) {
  138. if (item->from()->id == creatorId) {
  139. owner->requestItemResize(item);
  140. }
  141. }
  142. }
  143. }
  144. rpl::producer<MessagesSlice> RepliesList::source(
  145. MessagePosition aroundId,
  146. int limitBefore,
  147. int limitAfter) {
  148. const auto around = aroundId.fullId.msg;
  149. return [=](auto consumer) {
  150. auto lifetime = rpl::lifetime();
  151. const auto viewer = lifetime.make_state<Viewer>();
  152. const auto push = [=] {
  153. if (viewer->scheduled) {
  154. viewer->scheduled = false;
  155. if (buildFromData(viewer)) {
  156. appendClientSideMessages(viewer->slice);
  157. consumer.put_next_copy(viewer->slice);
  158. }
  159. }
  160. };
  161. const auto pushInstant = [=] {
  162. viewer->scheduled = true;
  163. push();
  164. };
  165. const auto pushDelayed = [=] {
  166. if (!viewer->scheduled) {
  167. viewer->scheduled = true;
  168. crl::on_main(&viewer->guard, push);
  169. }
  170. };
  171. viewer->around = around;
  172. viewer->limitBefore = limitBefore;
  173. viewer->limitAfter = limitAfter;
  174. _history->session().changes().historyUpdates(
  175. _history,
  176. HistoryUpdate::Flag::ClientSideMessages
  177. ) | rpl::start_with_next(pushDelayed, lifetime);
  178. _history->session().changes().messageUpdates(
  179. MessageUpdate::Flag::Destroyed
  180. ) | rpl::filter([=](const MessageUpdate &update) {
  181. return applyItemDestroyed(viewer, update.item);
  182. }) | rpl::start_with_next(pushDelayed, lifetime);
  183. _listChanges.events(
  184. ) | rpl::start_with_next(pushDelayed, lifetime);
  185. _instantChanges.events(
  186. ) | rpl::start_with_next(pushInstant, lifetime);
  187. pushInstant();
  188. return lifetime;
  189. };
  190. }
  191. void RepliesList::appendClientSideMessages(MessagesSlice &slice) {
  192. const auto &messages = _history->clientSideMessages();
  193. if (messages.empty()) {
  194. return;
  195. } else if (slice.ids.empty()) {
  196. if (slice.skippedBefore != 0 || slice.skippedAfter != 0) {
  197. return;
  198. }
  199. slice.ids.reserve(messages.size());
  200. for (const auto &item : messages) {
  201. if (!item->inThread(_rootId)) {
  202. continue;
  203. }
  204. slice.ids.push_back(item->fullId());
  205. }
  206. ranges::sort(slice.ids);
  207. return;
  208. }
  209. auto &owner = _history->owner();
  210. auto dates = std::vector<TimeId>();
  211. dates.reserve(slice.ids.size());
  212. for (const auto &id : slice.ids) {
  213. const auto message = owner.message(id);
  214. Assert(message != nullptr);
  215. dates.push_back(message->date());
  216. }
  217. for (const auto &item : messages) {
  218. if (!item->inThread(_rootId)) {
  219. continue;
  220. }
  221. const auto date = item->date();
  222. if (date < dates.front()) {
  223. if (slice.skippedBefore != 0) {
  224. if (slice.skippedBefore) {
  225. ++*slice.skippedBefore;
  226. }
  227. continue;
  228. }
  229. dates.insert(dates.begin(), date);
  230. slice.ids.insert(slice.ids.begin(), item->fullId());
  231. } else {
  232. auto to = dates.size();
  233. for (; to != 0; --to) {
  234. const auto checkId = slice.ids[to - 1].msg;
  235. if (dates[to - 1] > date) {
  236. continue;
  237. } else if (dates[to - 1] < date
  238. || IsServerMsgId(checkId)
  239. || checkId < item->id) {
  240. break;
  241. }
  242. }
  243. dates.insert(dates.begin() + to, date);
  244. slice.ids.insert(slice.ids.begin() + to, item->fullId());
  245. }
  246. }
  247. }
  248. rpl::producer<int> RepliesList::fullCount() const {
  249. return _fullCount.value() | rpl::filter_optional();
  250. }
  251. rpl::producer<std::optional<int>> RepliesList::maybeFullCount() const {
  252. return _fullCount.value();
  253. }
  254. bool RepliesList::unreadCountKnown() const {
  255. return _unreadCount.current().has_value();
  256. }
  257. int RepliesList::unreadCountCurrent() const {
  258. return _unreadCount.current().value_or(0);
  259. }
  260. rpl::producer<std::optional<int>> RepliesList::unreadCountValue() const {
  261. return _unreadCount.value();
  262. }
  263. void RepliesList::injectRootMessageAndReverse(not_null<Viewer*> viewer) {
  264. injectRootMessage(viewer);
  265. ranges::reverse(viewer->slice.ids);
  266. }
  267. void RepliesList::injectRootMessage(not_null<Viewer*> viewer) {
  268. const auto slice = &viewer->slice;
  269. viewer->injectedForRoot = 0;
  270. if (slice->skippedBefore != 0) {
  271. return;
  272. }
  273. const auto root = lookupRoot();
  274. if (!root
  275. || (_rootId == Data::ForumTopic::kGeneralId)
  276. || (root->topicRootId() != Data::ForumTopic::kGeneralId)) {
  277. return;
  278. }
  279. injectRootDivider(root, slice);
  280. if (const auto group = _history->owner().groups().find(root)) {
  281. for (const auto &item : ranges::views::reverse(group->items)) {
  282. slice->ids.push_back(item->fullId());
  283. }
  284. viewer->injectedForRoot = group->items.size();
  285. if (slice->fullCount) {
  286. *slice->fullCount += group->items.size();
  287. }
  288. } else {
  289. slice->ids.push_back(root->fullId());
  290. viewer->injectedForRoot = 1;
  291. }
  292. if (slice->fullCount) {
  293. *slice->fullCount += viewer->injectedForRoot;
  294. }
  295. }
  296. void RepliesList::injectRootDivider(
  297. not_null<HistoryItem*> root,
  298. not_null<MessagesSlice*> slice) {
  299. const auto withComments = !slice->ids.empty();
  300. const auto text = [&] {
  301. return withComments
  302. ? tr::lng_replies_discussion_started(tr::now)
  303. : tr::lng_replies_no_comments(tr::now);
  304. };
  305. if (!_divider) {
  306. _dividerWithComments = withComments;
  307. _divider = GenerateDivider(
  308. _history,
  309. root->date(),
  310. text());
  311. } else if (_dividerWithComments != withComments) {
  312. _dividerWithComments = withComments;
  313. _divider->updateServiceText(PreparedServiceText{ { text() } });
  314. }
  315. slice->ids.push_back(_divider->fullId());
  316. }
  317. bool RepliesList::buildFromData(not_null<Viewer*> viewer) {
  318. if (_creating
  319. || (_list.empty() && _skippedBefore == 0 && _skippedAfter == 0)) {
  320. viewer->slice.ids.clear();
  321. viewer->slice.nearestToAround = FullMsgId();
  322. viewer->slice.fullCount
  323. = viewer->slice.skippedBefore
  324. = viewer->slice.skippedAfter
  325. = 0;
  326. viewer->injectedForRoot = 0;
  327. injectRootMessageAndReverse(viewer);
  328. return true;
  329. }
  330. const auto around = [&] {
  331. if (viewer->around != ShowAtUnreadMsgId) {
  332. return viewer->around;
  333. } else if (const auto item = lookupRoot()) {
  334. return computeInboxReadTillFull();
  335. } else if (_owningTopic) {
  336. // Somehow we don't want always to jump to computed inboxReadTill
  337. // (this was in the code before, but I don't remember why).
  338. // Maybe in case we "View Thread" from a group we don't really
  339. // want to jump to unread inside thread, cause it isn't defined.
  340. //
  341. // But in case of topics we definitely want to support jumping
  342. // to the first unread, even if it is General topic without the
  343. // actual root message or it is a broken topic without root.
  344. return computeInboxReadTillFull();
  345. }
  346. return viewer->around;
  347. }();
  348. if (_list.empty()
  349. || (!around && _skippedAfter != 0)
  350. || (around > _list.front() && _skippedAfter != 0)
  351. || (around > 0 && around < _list.back() && _skippedBefore != 0)) {
  352. loadAround(around);
  353. return false;
  354. }
  355. const auto i = around
  356. ? ranges::lower_bound(_list, around, std::greater<>())
  357. : end(_list);
  358. const auto availableBefore = int(end(_list) - i);
  359. const auto availableAfter = int(i - begin(_list));
  360. const auto useBefore = std::min(availableBefore, viewer->limitBefore + 1);
  361. const auto useAfter = std::min(availableAfter, viewer->limitAfter);
  362. const auto slice = &viewer->slice;
  363. if (_skippedBefore.has_value()) {
  364. slice->skippedBefore
  365. = (*_skippedBefore + (availableBefore - useBefore));
  366. }
  367. if (_skippedAfter.has_value()) {
  368. slice->skippedAfter
  369. = (*_skippedAfter + (availableAfter - useAfter));
  370. }
  371. const auto peerId = _history->peer->id;
  372. slice->ids.clear();
  373. auto nearestToAround = std::optional<MsgId>();
  374. slice->ids.reserve(useAfter + useBefore);
  375. for (auto j = i - useAfter, e = i + useBefore; j != e; ++j) {
  376. const auto id = *j;
  377. if (id == _rootId) {
  378. continue;
  379. } else if (!nearestToAround && id < around) {
  380. nearestToAround = (j == i - useAfter)
  381. ? id
  382. : *(j - 1);
  383. }
  384. slice->ids.emplace_back(peerId, id);
  385. }
  386. slice->nearestToAround = FullMsgId(
  387. peerId,
  388. nearestToAround.value_or(
  389. slice->ids.empty() ? 0 : slice->ids.back().msg));
  390. slice->fullCount = _fullCount.current();
  391. injectRootMessageAndReverse(viewer);
  392. if (_skippedBefore != 0 && useBefore < viewer->limitBefore + 1) {
  393. loadBefore();
  394. }
  395. if (_skippedAfter != 0 && useAfter < viewer->limitAfter) {
  396. loadAfter();
  397. }
  398. return true;
  399. }
  400. bool RepliesList::applyItemDestroyed(
  401. not_null<Viewer*> viewer,
  402. not_null<HistoryItem*> item) {
  403. if (item->history() != _history || !item->isRegular()) {
  404. return false;
  405. }
  406. const auto fullId = item->fullId();
  407. for (auto i = 0; i != viewer->injectedForRoot; ++i) {
  408. if (viewer->slice.ids[i] == fullId) {
  409. return true;
  410. }
  411. }
  412. return false;
  413. }
  414. bool RepliesList::applyUpdate(const MessageUpdate &update) {
  415. using Flag = MessageUpdate::Flag;
  416. if (update.item->history() != _history
  417. || !update.item->isRegular()
  418. || !update.item->inThread(_rootId)) {
  419. return false;
  420. }
  421. const auto id = update.item->id;
  422. const auto added = (update.flags & Flag::ReplyToTopAdded);
  423. const auto i = ranges::lower_bound(_list, id, std::greater<>());
  424. if (update.flags & Flag::Destroyed) {
  425. if (!added) {
  426. changeUnreadCountByPost(id, -1);
  427. }
  428. if (i == end(_list) || *i != id) {
  429. return false;
  430. }
  431. _list.erase(i);
  432. if (_skippedBefore && _skippedAfter) {
  433. _fullCount = *_skippedBefore + _list.size() + *_skippedAfter;
  434. } else if (const auto known = _fullCount.current()) {
  435. if (*known > 0) {
  436. _fullCount = (*known - 1);
  437. }
  438. }
  439. return true;
  440. }
  441. if (added) {
  442. changeUnreadCountByPost(id, 1);
  443. }
  444. if (_skippedAfter != 0
  445. || (i != end(_list) && *i == id)) {
  446. return false;
  447. }
  448. _list.insert(i, id);
  449. if (_skippedBefore && _skippedAfter) {
  450. _fullCount = *_skippedBefore + _list.size() + *_skippedAfter;
  451. } else if (const auto known = _fullCount.current()) {
  452. _fullCount = *known + 1;
  453. }
  454. return true;
  455. }
  456. void RepliesList::applyDifferenceTooLong() {
  457. if (!_creating && _skippedAfter.has_value()) {
  458. _skippedAfter = std::nullopt;
  459. _listChanges.fire({});
  460. }
  461. }
  462. void RepliesList::changeUnreadCountByPost(MsgId id, int delta) {
  463. if (!_inboxReadTillId) {
  464. setUnreadCount(std::nullopt);
  465. return;
  466. }
  467. const auto count = _unreadCount.current();
  468. if (count.has_value() && (id > _inboxReadTillId)) {
  469. setUnreadCount(std::max(*count + delta, 0));
  470. }
  471. }
  472. Histories &RepliesList::histories() {
  473. return _history->owner().histories();
  474. }
  475. HistoryItem *RepliesList::lookupRoot() {
  476. return _history->owner().message(_history->peer->id, _rootId);
  477. }
  478. void RepliesList::loadAround(MsgId id) {
  479. Expects(!_creating);
  480. if (_loadingAround && *_loadingAround == id) {
  481. return;
  482. }
  483. histories().cancelRequest(base::take(_beforeId));
  484. histories().cancelRequest(base::take(_afterId));
  485. const auto send = [=](Fn<void()> finish) {
  486. return _history->session().api().request(MTPmessages_GetReplies(
  487. _history->peer->input,
  488. MTP_int(_rootId),
  489. MTP_int(id), // offset_id
  490. MTP_int(0), // offset_date
  491. MTP_int(id ? (-kMessagesPerPage / 2) : 0), // add_offset
  492. MTP_int(kMessagesPerPage), // limit
  493. MTP_int(0), // max_id
  494. MTP_int(0), // min_id
  495. MTP_long(0) // hash
  496. )).done([=](const MTPmessages_Messages &result) {
  497. _beforeId = 0;
  498. _loadingAround = std::nullopt;
  499. finish();
  500. if (!id) {
  501. _skippedAfter = 0;
  502. } else {
  503. _skippedAfter = std::nullopt;
  504. }
  505. _skippedBefore = std::nullopt;
  506. _list.clear();
  507. if (processMessagesIsEmpty(result)) {
  508. _fullCount = _skippedBefore = _skippedAfter = 0;
  509. } else if (id) {
  510. Assert(!_list.empty());
  511. if (_list.front() <= id) {
  512. _skippedAfter = 0;
  513. } else if (_list.back() >= id) {
  514. _skippedBefore = 0;
  515. }
  516. }
  517. checkReadTillEnd();
  518. }).fail([=] {
  519. _beforeId = 0;
  520. _loadingAround = std::nullopt;
  521. finish();
  522. }).send();
  523. };
  524. _loadingAround = id;
  525. _beforeId = histories().sendRequest(
  526. _history,
  527. Histories::RequestType::History,
  528. send);
  529. }
  530. void RepliesList::loadBefore() {
  531. Expects(!_list.empty());
  532. if (_loadingAround) {
  533. histories().cancelRequest(base::take(_beforeId));
  534. } else if (_beforeId) {
  535. return;
  536. }
  537. const auto last = _list.back();
  538. const auto send = [=](Fn<void()> finish) {
  539. return _history->session().api().request(MTPmessages_GetReplies(
  540. _history->peer->input,
  541. MTP_int(_rootId),
  542. MTP_int(last), // offset_id
  543. MTP_int(0), // offset_date
  544. MTP_int(0), // add_offset
  545. MTP_int(kMessagesPerPage), // limit
  546. MTP_int(0), // min_id
  547. MTP_int(0), // max_id
  548. MTP_long(0) // hash
  549. )).done([=](const MTPmessages_Messages &result) {
  550. _beforeId = 0;
  551. finish();
  552. if (_list.empty()) {
  553. return;
  554. } else if (_list.back() != last) {
  555. loadBefore();
  556. } else if (processMessagesIsEmpty(result)) {
  557. _skippedBefore = 0;
  558. if (_skippedAfter == 0) {
  559. _fullCount = _list.size();
  560. }
  561. }
  562. }).fail([=] {
  563. _beforeId = 0;
  564. finish();
  565. }).send();
  566. };
  567. _beforeId = histories().sendRequest(
  568. _history,
  569. Histories::RequestType::History,
  570. send);
  571. }
  572. void RepliesList::loadAfter() {
  573. Expects(!_list.empty());
  574. if (_afterId) {
  575. return;
  576. }
  577. const auto first = _list.front();
  578. const auto send = [=](Fn<void()> finish) {
  579. return _history->session().api().request(MTPmessages_GetReplies(
  580. _history->peer->input,
  581. MTP_int(_rootId),
  582. MTP_int(first + 1), // offset_id
  583. MTP_int(0), // offset_date
  584. MTP_int(-kMessagesPerPage), // add_offset
  585. MTP_int(kMessagesPerPage), // limit
  586. MTP_int(0), // min_id
  587. MTP_int(0), // max_id
  588. MTP_long(0) // hash
  589. )).done([=](const MTPmessages_Messages &result) {
  590. _afterId = 0;
  591. finish();
  592. if (_list.empty()) {
  593. return;
  594. } else if (_list.front() != first) {
  595. loadAfter();
  596. } else if (processMessagesIsEmpty(result)) {
  597. _skippedAfter = 0;
  598. if (_skippedBefore == 0) {
  599. _fullCount = _list.size();
  600. }
  601. checkReadTillEnd();
  602. }
  603. }).fail([=] {
  604. _afterId = 0;
  605. finish();
  606. }).send();
  607. };
  608. _afterId = histories().sendRequest(
  609. _history,
  610. Histories::RequestType::History,
  611. send);
  612. }
  613. bool RepliesList::processMessagesIsEmpty(const MTPmessages_Messages &result) {
  614. const auto guard = gsl::finally([&] { _listChanges.fire({}); });
  615. auto &owner = _history->owner();
  616. const auto list = result.match([&](
  617. const MTPDmessages_messagesNotModified &) {
  618. LOG(("API Error: received messages.messagesNotModified! "
  619. "(HistoryWidget::messagesReceived)"));
  620. return QVector<MTPMessage>();
  621. }, [&](const auto &data) {
  622. owner.processUsers(data.vusers());
  623. owner.processChats(data.vchats());
  624. return data.vmessages().v;
  625. });
  626. const auto fullCount = result.match([&](
  627. const MTPDmessages_messagesNotModified &) {
  628. LOG(("API Error: received messages.messagesNotModified! "
  629. "(HistoryWidget::messagesReceived)"));
  630. return 0;
  631. }, [&](const MTPDmessages_messages &data) {
  632. return int(data.vmessages().v.size());
  633. }, [&](const MTPDmessages_messagesSlice &data) {
  634. return data.vcount().v;
  635. }, [&](const MTPDmessages_channelMessages &data) {
  636. if (const auto channel = _history->peer->asChannel()) {
  637. channel->ptsReceived(data.vpts().v);
  638. channel->processTopics(data.vtopics());
  639. } else {
  640. LOG(("API Error: received messages.channelMessages when "
  641. "no channel was passed! (HistoryWidget::messagesReceived)"));
  642. }
  643. return data.vcount().v;
  644. });
  645. if (list.isEmpty()) {
  646. return true;
  647. }
  648. const auto maxId = IdFromMessage(list.front());
  649. const auto wasSize = int(_list.size());
  650. const auto toFront = (wasSize > 0) && (maxId > _list.front());
  651. const auto localFlags = MessageFlags();
  652. const auto type = NewMessageType::Existing;
  653. auto refreshed = std::vector<MsgId>();
  654. if (toFront) {
  655. refreshed.reserve(_list.size() + list.size());
  656. }
  657. auto skipped = 0;
  658. for (const auto &message : list) {
  659. if (const auto item = owner.addNewMessage(message, localFlags, type)) {
  660. if (item->inThread(_rootId)) {
  661. if (toFront && item->id > _list.front()) {
  662. refreshed.push_back(item->id);
  663. } else if (_list.empty() || item->id < _list.back()) {
  664. _list.push_back(item->id);
  665. }
  666. } else {
  667. ++skipped;
  668. }
  669. } else {
  670. ++skipped;
  671. }
  672. }
  673. if (toFront) {
  674. refreshed.insert(refreshed.end(), _list.begin(), _list.end());
  675. _list = std::move(refreshed);
  676. }
  677. const auto nowSize = int(_list.size());
  678. auto &decrementFrom = toFront ? _skippedAfter : _skippedBefore;
  679. if (decrementFrom.has_value()) {
  680. *decrementFrom = std::max(
  681. *decrementFrom - (nowSize - wasSize),
  682. 0);
  683. }
  684. const auto checkedCount = std::max(fullCount - skipped, nowSize);
  685. if (_skippedBefore && _skippedAfter) {
  686. auto &correct = toFront ? _skippedBefore : _skippedAfter;
  687. *correct = std::max(
  688. checkedCount - *decrementFrom - nowSize,
  689. 0);
  690. *decrementFrom = checkedCount - *correct - nowSize;
  691. Assert(*decrementFrom >= 0);
  692. } else if (_skippedBefore) {
  693. *_skippedBefore = std::min(*_skippedBefore, checkedCount - nowSize);
  694. _skippedAfter = checkedCount - *_skippedBefore - nowSize;
  695. } else if (_skippedAfter) {
  696. *_skippedAfter = std::min(*_skippedAfter, checkedCount - nowSize);
  697. _skippedBefore = checkedCount - *_skippedAfter - nowSize;
  698. }
  699. _fullCount = checkedCount;
  700. checkReadTillEnd();
  701. if (const auto item = lookupRoot()) {
  702. if (const auto original = item->lookupDiscussionPostOriginal()) {
  703. if (_skippedAfter == 0 && !_list.empty()) {
  704. original->setCommentsMaxId(_list.front());
  705. } else {
  706. original->setCommentsPossibleMaxId(maxId);
  707. }
  708. }
  709. }
  710. Ensures(list.size() >= skipped);
  711. return (list.size() == skipped);
  712. }
  713. void RepliesList::setInboxReadTill(
  714. MsgId readTillId,
  715. std::optional<int> unreadCount) {
  716. const auto newReadTillId = std::max(readTillId.bare, int64(1));
  717. const auto ignore = (newReadTillId < _inboxReadTillId);
  718. if (ignore) {
  719. return;
  720. }
  721. const auto changed = (newReadTillId > _inboxReadTillId);
  722. if (changed) {
  723. _inboxReadTillId = newReadTillId;
  724. }
  725. if (_skippedAfter == 0
  726. && !_list.empty()
  727. && _inboxReadTillId >= _list.front()) {
  728. unreadCount = 0;
  729. }
  730. const auto wasUnreadCount = _unreadCount;
  731. if (_unreadCount.current() != unreadCount
  732. && (changed || unreadCount.has_value())) {
  733. setUnreadCount(unreadCount);
  734. }
  735. }
  736. MsgId RepliesList::inboxReadTillId() const {
  737. return _inboxReadTillId;
  738. }
  739. MsgId RepliesList::computeInboxReadTillFull() const {
  740. const auto local = _inboxReadTillId;
  741. if (const auto megagroup = _history->peer->asMegagroup()) {
  742. if (!megagroup->isForum() && megagroup->amIn()) {
  743. return std::max(local, _history->inboxReadTillId());
  744. }
  745. }
  746. return local;
  747. }
  748. void RepliesList::setOutboxReadTill(MsgId readTillId) {
  749. const auto newReadTillId = std::max(readTillId.bare, int64(1));
  750. if (newReadTillId > _outboxReadTillId) {
  751. _outboxReadTillId = newReadTillId;
  752. _history->session().changes().historyUpdated(
  753. _history,
  754. HistoryUpdate::Flag::OutboxRead);
  755. }
  756. }
  757. MsgId RepliesList::computeOutboxReadTillFull() const {
  758. const auto local = _outboxReadTillId;
  759. if (const auto megagroup = _history->peer->asMegagroup()) {
  760. if (!megagroup->isForum() && megagroup->amIn()) {
  761. return std::max(local, _history->outboxReadTillId());
  762. }
  763. }
  764. return local;
  765. }
  766. void RepliesList::setUnreadCount(std::optional<int> count) {
  767. _unreadCount = count;
  768. if (!count && !_readRequestTimer.isActive() && !_readRequestId) {
  769. reloadUnreadCountIfNeeded();
  770. }
  771. }
  772. int RepliesList::displayedUnreadCount() const {
  773. return (_inboxReadTillId > 1) ? unreadCountCurrent() : 0;
  774. }
  775. bool RepliesList::isServerSideUnread(
  776. not_null<const HistoryItem*> item) const {
  777. const auto till = item->out()
  778. ? computeOutboxReadTillFull()
  779. : computeInboxReadTillFull();
  780. return (item->id > till);
  781. }
  782. void RepliesList::checkReadTillEnd() {
  783. if (_unreadCount.current() != 0
  784. && _skippedAfter == 0
  785. && !_list.empty()
  786. && _inboxReadTillId >= _list.front()) {
  787. setUnreadCount(0);
  788. }
  789. }
  790. std::optional<int> RepliesList::computeUnreadCountLocally(
  791. MsgId afterId) const {
  792. Expects(afterId >= _inboxReadTillId);
  793. const auto currentUnreadCountAfter = _unreadCount.current();
  794. const auto startingMarkingAsRead = (currentUnreadCountAfter == 0)
  795. && (_inboxReadTillId == 1)
  796. && (afterId > 1);
  797. const auto wasUnreadCountAfter = startingMarkingAsRead
  798. ? _fullCount.current().value_or(0)
  799. : currentUnreadCountAfter;
  800. const auto readTillId = std::max(afterId, _rootId);
  801. const auto wasReadTillId = _inboxReadTillId;
  802. const auto backLoaded = (_skippedBefore == 0);
  803. const auto frontLoaded = (_skippedAfter == 0);
  804. const auto fullLoaded = backLoaded && frontLoaded;
  805. const auto allUnread = (readTillId == _rootId)
  806. || (fullLoaded && _list.empty());
  807. if (allUnread && fullLoaded) {
  808. // Should not happen too often unless the list is empty.
  809. return int(_list.size());
  810. } else if (frontLoaded && !_list.empty() && readTillId >= _list.front()) {
  811. // Always "count by local data" if read till the end.
  812. return 0;
  813. } else if (wasReadTillId == readTillId) {
  814. // Otherwise don't recount the same value over and over.
  815. return wasUnreadCountAfter;
  816. } else if (frontLoaded && !_list.empty() && readTillId >= _list.back()) {
  817. // And count by local data if it is available and read-till changed.
  818. return int(ranges::lower_bound(_list, readTillId, std::greater<>())
  819. - begin(_list));
  820. } else if (_list.empty()) {
  821. return std::nullopt;
  822. } else if (wasUnreadCountAfter.has_value()
  823. && (frontLoaded || readTillId <= _list.front())
  824. && (backLoaded || wasReadTillId >= _list.back())) {
  825. // Count how many were read since previous value.
  826. const auto from = ranges::lower_bound(
  827. _list,
  828. readTillId,
  829. std::greater<>());
  830. const auto till = ranges::lower_bound(
  831. from,
  832. end(_list),
  833. wasReadTillId,
  834. std::greater<>());
  835. return std::max(*wasUnreadCountAfter - int(till - from), 0);
  836. }
  837. return std::nullopt;
  838. }
  839. void RepliesList::requestUnreadCount() {
  840. if (_reloadUnreadCountRequestId) {
  841. return;
  842. }
  843. const auto weak = base::make_weak(this);
  844. const auto session = &_history->session();
  845. const auto fullId = FullMsgId(_history->peer->id, _rootId);
  846. const auto apply = [weak, session, fullId](
  847. MsgId readTill,
  848. int unreadCount) {
  849. if (const auto strong = weak.get()) {
  850. strong->setInboxReadTill(readTill, unreadCount);
  851. }
  852. if (const auto root = session->data().message(fullId)) {
  853. if (const auto post = root->lookupDiscussionPostOriginal()) {
  854. post->setCommentsInboxReadTill(readTill);
  855. }
  856. }
  857. };
  858. _reloadUnreadCountRequestId = session->api().request(
  859. MTPmessages_GetDiscussionMessage(
  860. _history->peer->input,
  861. MTP_int(_rootId))
  862. ).done([=](const MTPmessages_DiscussionMessage &result) {
  863. if (weak) {
  864. _reloadUnreadCountRequestId = 0;
  865. }
  866. result.match([&](const MTPDmessages_discussionMessage &data) {
  867. session->data().processUsers(data.vusers());
  868. session->data().processChats(data.vchats());
  869. apply(
  870. data.vread_inbox_max_id().value_or_empty(),
  871. data.vunread_count().v);
  872. });
  873. }).send();
  874. }
  875. void RepliesList::readTill(not_null<HistoryItem*> item) {
  876. readTill(item->id, item);
  877. }
  878. void RepliesList::readTill(MsgId tillId) {
  879. readTill(tillId, _history->owner().message(_history->peer->id, tillId));
  880. }
  881. void RepliesList::readTill(
  882. MsgId tillId,
  883. HistoryItem *tillIdItem) {
  884. if (!IsServerMsgId(tillId)) {
  885. return;
  886. }
  887. const auto was = computeInboxReadTillFull();
  888. const auto now = tillId;
  889. if (now < was) {
  890. return;
  891. }
  892. const auto unreadCount = computeUnreadCountLocally(now);
  893. const auto fast = (tillIdItem && tillIdItem->out()) || !unreadCount.has_value();
  894. if (was < now || (fast && now == was)) {
  895. setInboxReadTill(now, unreadCount);
  896. const auto rootFullId = FullMsgId(_history->peer->id, _rootId);
  897. if (const auto root = _history->owner().message(rootFullId)) {
  898. if (const auto post = root->lookupDiscussionPostOriginal()) {
  899. post->setCommentsInboxReadTill(now);
  900. }
  901. }
  902. if (!_readRequestTimer.isActive()) {
  903. _readRequestTimer.callOnce(fast ? 0 : kReadRequestTimeout);
  904. } else if (fast && _readRequestTimer.remainingTime() > 0) {
  905. _readRequestTimer.callOnce(0);
  906. }
  907. }
  908. if (const auto topic = _history->peer->forumTopicFor(_rootId)) {
  909. Core::App().notifications().clearIncomingFromTopic(topic);
  910. }
  911. }
  912. void RepliesList::sendReadTillRequest() {
  913. if (_readRequestTimer.isActive()) {
  914. _readRequestTimer.cancel();
  915. }
  916. const auto api = &_history->session().api();
  917. api->request(base::take(_readRequestId)).cancel();
  918. _readRequestId = api->request(MTPmessages_ReadDiscussion(
  919. _history->peer->input,
  920. MTP_int(_rootId),
  921. MTP_int(computeInboxReadTillFull())
  922. )).done(crl::guard(this, [=] {
  923. _readRequestId = 0;
  924. reloadUnreadCountIfNeeded();
  925. })).send();
  926. }
  927. void RepliesList::reloadUnreadCountIfNeeded() {
  928. if (unreadCountKnown()) {
  929. return;
  930. } else if (inboxReadTillId() < computeInboxReadTillFull()) {
  931. _readRequestTimer.callOnce(0);
  932. } else {
  933. requestUnreadCount();
  934. }
  935. }
  936. bool RepliesList::canDeleteMyTopic() const {
  937. if (_skippedBefore != 0 || _skippedAfter != 0) {
  938. return false;
  939. }
  940. auto counter = 0;
  941. const auto owner = &_history->owner();
  942. const auto peerId = _history->peer->id;
  943. for (const auto &id : _list) {
  944. if (id == _rootId) {
  945. continue;
  946. } else if (const auto item = owner->message(peerId, id)) {
  947. if (!item->out() || ++counter > kMaxMessagesToDeleteMyTopic) {
  948. return false;
  949. }
  950. } else {
  951. return false;
  952. }
  953. }
  954. return true;
  955. }
  956. } // namespace Data