/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_histories.h" #include "data/data_session.h" #include "data/data_channel.h" #include "main/main_session.h" #include "history/history.h" #include "history/history_item.h" #include "history/view/history_view_element.h" #include "apiwrap.h" namespace Data { namespace { constexpr auto kReadRequestTimeout = 3 * crl::time(1000); constexpr auto kReadRequestSent = std::numeric_limits::max(); } // namespace Histories::Histories(not_null owner) : _owner(owner) , _readRequestsTimer([=] { sendReadRequests(); }) { } Session &Histories::owner() const { return *_owner; } Main::Session &Histories::session() const { return _owner->session(); } History *Histories::find(PeerId peerId) { const auto i = peerId ? _map.find(peerId) : end(_map); return (i != end(_map)) ? i->second.get() : nullptr; } not_null Histories::findOrCreate(PeerId peerId) { Expects(peerId != 0); if (const auto result = find(peerId)) { return result; } const auto [i, ok] = _map.emplace( peerId, std::make_unique(&owner(), peerId)); return i->second.get(); } void Histories::unloadAll() { for (const auto &[peerId, history] : _map) { history->clear(History::ClearType::Unload); } } void Histories::clearAll() { _map.clear(); } void Histories::readInboxTill( not_null history, not_null item) { if (!IsServerMsgId(item->id)) { auto view = item->mainView(); if (!view) { return; } auto block = view->block(); auto blockIndex = block->indexInHistory(); auto itemIndex = view->indexInBlock(); while (blockIndex > 0 || itemIndex > 0) { if (itemIndex > 0) { view = block->messages[--itemIndex].get(); } else { while (blockIndex > 0) { block = history->blocks[--blockIndex].get(); itemIndex = block->messages.size(); if (itemIndex > 0) { view = block->messages[--itemIndex].get(); break; } } } item = view->data(); if (IsServerMsgId(item->id)) { break; } } if (!IsServerMsgId(item->id)) { LOG(("App Error: " "Can't read history till unknown local message.")); return; } } const auto tillId = item->id; if (!history->readInboxTillNeedsRequest(tillId)) { return; } const auto maybeState = lookup(history); if (maybeState && maybeState->readTill >= tillId) { return; } const auto stillUnread = history->countStillUnreadLocal(tillId); if (stillUnread && history->unreadCountKnown() && *stillUnread == history->unreadCount()) { history->setInboxReadTill(tillId); return; } auto &state = _states[history]; const auto wasWaiting = (state.readTill != 0); state.readTill = tillId; if (!stillUnread) { state.readWhen = 0; sendReadRequests(); return; } else if (!wasWaiting) { state.readWhen = crl::now() + kReadRequestTimeout; if (!_readRequestsTimer.isActive()) { _readRequestsTimer.callOnce(kReadRequestTimeout); } } history->setInboxReadTill(tillId); history->setUnreadCount(*stillUnread); history->updateChatListEntry(); } void Histories::sendPendingReadInbox(not_null history) { if (const auto state = lookup(history)) { if (state->readTill && state->readWhen) { state->readWhen = 0; sendReadRequests(); } } } void Histories::sendReadRequests() { if (_states.empty()) { return; } const auto now = crl::now(); auto next = std::optional(); for (auto &[history, state] : _states) { if (state.readTill && state.readWhen <= now) { sendReadRequest(history, state); } else if (!next || *next > state.readWhen) { next = state.readWhen; } } if (next.has_value()) { _readRequestsTimer.callOnce(*next - now); } } void Histories::sendReadRequest(not_null history, State &state) { const auto tillId = state.readTill; state.readWhen = kReadRequestSent; sendRequest(history, RequestType::ReadInbox, [=](Fn done) { const auto finished = [=] { const auto state = lookup(history); Assert(state != nullptr); if (history->unreadCountRefreshNeeded(tillId)) { session().api().requestDialogEntry(history); } if (state->readWhen == kReadRequestSent) { state->readWhen = 0; state->readTill = 0; } done(); }; if (const auto channel = history->peer->asChannel()) { return session().api().request(MTPchannels_ReadHistory( channel->inputChannel, MTP_int(tillId) )).done([=](const MTPBool &result) { finished(); }).fail([=](const RPCError &error) { finished(); }).send(); } else { return session().api().request(MTPmessages_ReadHistory( history->peer->input, MTP_int(tillId) )).done([=](const MTPmessages_AffectedMessages &result) { session().api().applyAffectedMessages(history->peer, result); finished(); }).fail([=](const RPCError &error) { finished(); }).send(); } }); } void Histories::checkEmptyState(not_null history) { const auto empty = [](const State &state) { return state.postponed.empty() && state.sent.empty() && (state.readTill == 0); }; const auto i = _states.find(history); if (i != end(_states) && empty(i->second)) { _states.erase(i); } } int Histories::sendRequest( not_null history, RequestType type, Fn done)> generator) { Expects(type != RequestType::None); auto &state = _states[history]; const auto id = ++state.autoincrement; const auto action = chooseAction(state, type); if (action == Action::Send) { state.sent.emplace(id, SentRequest{ generator([=] { checkPostponed(history, id); }), type }); if (base::take(state.thenRequestEntry)) { session().api().requestDialogEntry(history); } } else if (action == Action::Postpone) { state.postponed.emplace( id, PostponedRequest{ std::move(generator), type }); } return id; } void Histories::checkPostponed(not_null history, int requestId) { const auto state = lookup(history); Assert(state != nullptr); state->sent.remove(requestId); if (!state->postponed.empty()) { auto &entry = state->postponed.front(); const auto action = chooseAction(*state, entry.second.type, true); if (action == Action::Send) { const auto id = entry.first; state->postponed.remove(id); state->sent.emplace(id, SentRequest{ entry.second.generator([=] { checkPostponed(history, id); }), entry.second.type }); if (base::take(state->thenRequestEntry)) { session().api().requestDialogEntry(history); } } else { Assert(action == Action::Postpone); } } checkEmptyState(history); } Histories::Action Histories::chooseAction( State &state, RequestType type, bool fromPostponed) const { switch (type) { case RequestType::ReadInbox: for (const auto &[_, sent] : state.sent) { if (sent.type == RequestType::ReadInbox || sent.type == RequestType::DialogsEntry || sent.type == RequestType::Delete) { if (!fromPostponed) { auto &postponed = state.postponed; for (auto i = begin(postponed); i != end(postponed);) { if (i->second.type == RequestType::ReadInbox) { i = postponed.erase(i); } else { ++i; } } } return Action::Postpone; } } return Action::Send; case RequestType::DialogsEntry: for (const auto &[_, sent] : state.sent) { if (sent.type == RequestType::DialogsEntry) { return Action::Skip; } if (sent.type == RequestType::ReadInbox || sent.type == RequestType::Delete) { if (!fromPostponed) { auto &postponed = state.postponed; for (const auto &[_, postponed] : state.postponed) { if (postponed.type == RequestType::DialogsEntry) { return Action::Skip; } } } return Action::Postpone; } } return Action::Send; case RequestType::History: for (const auto &[_, sent] : state.sent) { if (sent.type == RequestType::Delete) { return Action::Postpone; } } return Action::Send; case RequestType::Delete: for (const auto &[_, sent] : state.sent) { if (sent.type == RequestType::History || sent.type == RequestType::ReadInbox) { return Action::Postpone; } } for (auto i = begin(state.sent); i != end(state.sent);) { if (i->second.type == RequestType::DialogsEntry) { session().api().request(i->second.id).cancel(); i = state.sent.erase(i); state.thenRequestEntry = true; } } return Action::Send; } Unexpected("Request type in Histories::chooseAction."); } Histories::State *Histories::lookup(not_null history) { const auto i = _states.find(history); return (i != end(_states)) ? &i->second : nullptr; } } // namespace Data