1147 lines
		
	
	
	
		
			32 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1147 lines
		
	
	
	
		
			32 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
| 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 "api/api_text_entities.h"
 | |
| #include "data/data_session.h"
 | |
| #include "data/data_channel.h"
 | |
| #include "data/data_chat.h"
 | |
| #include "data/data_folder.h"
 | |
| #include "data/data_forum.h"
 | |
| #include "data/data_forum_topic.h"
 | |
| #include "data/data_scheduled_messages.h"
 | |
| #include "data/data_user.h"
 | |
| #include "base/unixtime.h"
 | |
| #include "base/random.h"
 | |
| #include "main/main_session.h"
 | |
| #include "window/notifications_manager.h"
 | |
| #include "history/history.h"
 | |
| #include "history/history_item.h"
 | |
| #include "history/history_item_helpers.h"
 | |
| #include "history/view/history_view_element.h"
 | |
| #include "core/application.h"
 | |
| #include "apiwrap.h"
 | |
| 
 | |
| namespace Data {
 | |
| namespace {
 | |
| 
 | |
| constexpr auto kReadRequestTimeout = 3 * crl::time(1000);
 | |
| 
 | |
| } // namespace
 | |
| 
 | |
| MTPInputReplyTo ReplyToForMTP(
 | |
| 		not_null<History*> history,
 | |
| 		FullReplyTo replyTo) {
 | |
| 	const auto owner = &history->owner();
 | |
| 	if (replyTo.storyId) {
 | |
| 		if (const auto peer = owner->peerLoaded(replyTo.storyId.peer)) {
 | |
| 			if (const auto user = peer->asUser()) {
 | |
| 				return MTP_inputReplyToStory(
 | |
| 					user->inputUser,
 | |
| 					MTP_int(replyTo.storyId.story));
 | |
| 			}
 | |
| 		}
 | |
| 	} else if (replyTo.messageId || replyTo.topicRootId) {
 | |
| 		const auto to = LookupReplyTo(history, replyTo.messageId);
 | |
| 		const auto replyingToTopic = replyTo.topicRootId
 | |
| 			? history->peer->forumTopicFor(replyTo.topicRootId)
 | |
| 			: nullptr;
 | |
| 		const auto replyingToTopicId = replyTo.topicRootId
 | |
| 			? (replyingToTopic
 | |
| 				? replyingToTopic->rootId()
 | |
| 				: Data::ForumTopic::kGeneralId)
 | |
| 			: (to ? to->topicRootId() : Data::ForumTopic::kGeneralId);
 | |
| 		const auto replyToTopicId = to
 | |
| 			? to->topicRootId()
 | |
| 			: replyingToTopicId;
 | |
| 		const auto external = replyTo.messageId
 | |
| 			&& (replyTo.messageId.peer != history->peer->id
 | |
| 				|| replyingToTopicId != replyToTopicId);
 | |
| 		const auto quoteEntities = Api::EntitiesToMTP(
 | |
| 			&history->session(),
 | |
| 			replyTo.quote.entities,
 | |
| 			Api::ConvertOption::SkipLocal);
 | |
| 		using Flag = MTPDinputReplyToMessage::Flag;
 | |
| 		return MTP_inputReplyToMessage(
 | |
| 			MTP_flags((replyTo.topicRootId ? Flag::f_top_msg_id : Flag())
 | |
| 				| (external ? Flag::f_reply_to_peer_id : Flag())
 | |
| 				| (replyTo.quote.text.isEmpty()
 | |
| 					? Flag()
 | |
| 					: Flag::f_quote_text)
 | |
| 				| (quoteEntities.v.isEmpty()
 | |
| 					? Flag()
 | |
| 					: Flag::f_quote_entities)),
 | |
| 			MTP_int(replyTo.messageId ? replyTo.messageId.msg : 0),
 | |
| 			MTP_int(replyTo.topicRootId),
 | |
| 			(external
 | |
| 				? owner->peer(replyTo.messageId.peer)->input
 | |
| 				: MTPInputPeer()),
 | |
| 			MTP_string(replyTo.quote.text),
 | |
| 			quoteEntities);
 | |
| 	}
 | |
| 	return MTPInputReplyTo();
 | |
| }
 | |
| 
 | |
| MTPInputMedia WebPageForMTP(
 | |
| 		const Data::WebPageDraft &draft,
 | |
| 		bool required) {
 | |
| 	using Flag = MTPDinputMediaWebPage::Flag;
 | |
| 	return MTP_inputMediaWebPage(
 | |
| 		MTP_flags((required ? Flag() : Flag::f_optional)
 | |
| 			| (draft.forceLargeMedia ? Flag::f_force_large_media : Flag())
 | |
| 			| (draft.forceSmallMedia ? Flag::f_force_small_media : Flag())),
 | |
| 		MTP_string(draft.url));
 | |
| }
 | |
| 
 | |
| Histories::Histories(not_null<Session*> 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<History*> 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<History>(&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::readInbox(not_null<History*> history) {
 | |
| 	DEBUG_LOG(("Reading: readInbox called."));
 | |
| 	if (history->lastServerMessageKnown()) {
 | |
| 		const auto last = history->lastServerMessage();
 | |
| 		DEBUG_LOG(("Reading: last known, reading till %1."
 | |
| 			).arg(last ? last->id.bare : 0));
 | |
| 		readInboxTill(history, last ? last->id : 0);
 | |
| 		return;
 | |
| 	} else if (history->loadedAtBottom()) {
 | |
| 		if (const auto lastId = history->maxMsgId()) {
 | |
| 			DEBUG_LOG(("Reading: loaded at bottom, maxMsgId %1."
 | |
| 				).arg(lastId.bare));
 | |
| 			readInboxTill(history, lastId);
 | |
| 			return;
 | |
| 		} else if (history->loadedAtTop()) {
 | |
| 			DEBUG_LOG(("Reading: loaded at bottom, loaded at top."));
 | |
| 			readInboxTill(history, 0);
 | |
| 			return;
 | |
| 		}
 | |
| 		DEBUG_LOG(("Reading: loaded at bottom, but requesting entry."));
 | |
| 	}
 | |
| 	requestDialogEntry(history, [=] {
 | |
| 		Expects(history->lastServerMessageKnown());
 | |
| 
 | |
| 		const auto last = history->lastServerMessage();
 | |
| 		DEBUG_LOG(("Reading: got entry, reading till %1."
 | |
| 			).arg(last ? last->id.bare : 0));
 | |
| 		readInboxTill(history, last ? last->id : 0);
 | |
| 	});
 | |
| }
 | |
| 
 | |
| void Histories::readInboxTill(not_null<HistoryItem*> item) {
 | |
| 	const auto history = item->history();
 | |
| 	if (!item->isRegular()) {
 | |
| 		readClientSideMessage(item);
 | |
| 		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 (item->isRegular()) {
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 		if (!item->isRegular()) {
 | |
| 			LOG(("App Error: "
 | |
| 				"Can't read history till unknown local message."));
 | |
| 			return;
 | |
| 		}
 | |
| 	}
 | |
| 	readInboxTill(history, item->id);
 | |
| }
 | |
| 
 | |
| void Histories::readInboxTill(not_null<History*> history, MsgId tillId) {
 | |
| 	readInboxTill(history, tillId, false);
 | |
| }
 | |
| 
 | |
| void Histories::readInboxTill(
 | |
| 		not_null<History*> history,
 | |
| 		MsgId tillId,
 | |
| 		bool force) {
 | |
| 	Expects(IsServerMsgId(tillId) || (!tillId && !force));
 | |
| 
 | |
| 	DEBUG_LOG(("Reading: readInboxTill %1, force %2."
 | |
| 		).arg(tillId.bare
 | |
| 		).arg(Logs::b(force)));
 | |
| 
 | |
| 	const auto syncGuard = gsl::finally([&] {
 | |
| 		DEBUG_LOG(("Reading: in guard, unread %1."
 | |
| 			).arg(history->unreadCount()));
 | |
| 		if (history->unreadCount() > 0) {
 | |
| 			if (const auto last = history->lastServerMessage()) {
 | |
| 				DEBUG_LOG(("Reading: checking last %1 and %2."
 | |
| 					).arg(last->id.bare
 | |
| 					).arg(tillId.bare));
 | |
| 				if (last->id == tillId) {
 | |
| 					DEBUG_LOG(("Reading: locally marked as read."));
 | |
| 					history->setUnreadCount(0);
 | |
| 					history->updateChatListEntry();
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	});
 | |
| 
 | |
| 	Core::App().notifications().clearIncomingFromHistory(history);
 | |
| 
 | |
| 	const auto needsRequest = history->readInboxTillNeedsRequest(tillId);
 | |
| 	if (!needsRequest && !force) {
 | |
| 		DEBUG_LOG(("Reading: readInboxTill finish 1."));
 | |
| 		return;
 | |
| 	} else if (!history->trackUnreadMessages()) {
 | |
| 		DEBUG_LOG(("Reading: readInboxTill finish 2."));
 | |
| 		return;
 | |
| 	}
 | |
| 	const auto maybeState = lookup(history);
 | |
| 	if (maybeState && maybeState->sentReadTill >= tillId) {
 | |
| 		DEBUG_LOG(("Reading: readInboxTill finish 3 with %1."
 | |
| 			).arg(maybeState->sentReadTill.bare));
 | |
| 		return;
 | |
| 	} else if (maybeState && maybeState->willReadTill >= tillId) {
 | |
| 		DEBUG_LOG(("Reading: readInboxTill finish 4 with %1 and force %2."
 | |
| 			).arg(maybeState->sentReadTill.bare
 | |
| 			).arg(Logs::b(force)));
 | |
| 		if (force) {
 | |
| 			sendPendingReadInbox(history);
 | |
| 		}
 | |
| 		return;
 | |
| 	} else if (!needsRequest
 | |
| 		&& (!maybeState || !maybeState->willReadTill)) {
 | |
| 		return;
 | |
| 	}
 | |
| 	const auto stillUnread = history->countStillUnreadLocal(tillId);
 | |
| 	if (!force
 | |
| 		&& stillUnread
 | |
| 		&& history->unreadCountKnown()
 | |
| 		&& *stillUnread == history->unreadCount()) {
 | |
| 		DEBUG_LOG(("Reading: count didn't change so just update till %1"
 | |
| 			).arg(tillId.bare));
 | |
| 		history->setInboxReadTill(tillId);
 | |
| 		return;
 | |
| 	}
 | |
| 	auto &state = maybeState ? *maybeState : _states[history];
 | |
| 	state.willReadTill = tillId;
 | |
| 	if (force || !stillUnread || !*stillUnread) {
 | |
| 		DEBUG_LOG(("Reading: will read till %1 with still unread %2"
 | |
| 			).arg(tillId.bare
 | |
| 			).arg(stillUnread.value_or(-666)));
 | |
| 		state.willReadWhen = 0;
 | |
| 		sendReadRequests();
 | |
| 		if (!stillUnread) {
 | |
| 			return;
 | |
| 		}
 | |
| 	} else if (!state.willReadWhen) {
 | |
| 		DEBUG_LOG(("Reading: will read till %1 with postponed"
 | |
| 			).arg(tillId.bare));
 | |
| 		state.willReadWhen = crl::now() + kReadRequestTimeout;
 | |
| 		if (!_readRequestsTimer.isActive()) {
 | |
| 			_readRequestsTimer.callOnce(kReadRequestTimeout);
 | |
| 		}
 | |
| 	} else {
 | |
| 		DEBUG_LOG(("Reading: will read till %1 postponed already"
 | |
| 			).arg(tillId.bare));
 | |
| 	}
 | |
| 	DEBUG_LOG(("Reading: marking now with till %1 and still %2"
 | |
| 		).arg(tillId.bare
 | |
| 		).arg(*stillUnread));
 | |
| 	history->setInboxReadTill(tillId);
 | |
| 	history->setUnreadCount(*stillUnread);
 | |
| 	history->updateChatListEntry();
 | |
| }
 | |
| 
 | |
| void Histories::readInboxOnNewMessage(not_null<HistoryItem*> item) {
 | |
| 	if (!item->isRegular()) {
 | |
| 		readClientSideMessage(item);
 | |
| 	} else {
 | |
| 		readInboxTill(item->history(), item->id, true);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void Histories::readClientSideMessage(not_null<HistoryItem*> item) {
 | |
| 	if (item->out() || !item->unread(item->history())) {
 | |
| 		return;
 | |
| 	}
 | |
| 	const auto history = item->history();
 | |
| 	item->markClientSideAsRead();
 | |
| 	if (const auto unread = history->unreadCount()) {
 | |
| 		history->setUnreadCount(unread - 1);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void Histories::requestDialogEntry(not_null<Data::Folder*> folder) {
 | |
| 	if (_dialogFolderRequests.contains(folder)) {
 | |
| 		return;
 | |
| 	}
 | |
| 	_dialogFolderRequests.emplace(folder);
 | |
| 
 | |
| 	auto peers = QVector<MTPInputDialogPeer>(
 | |
| 		1,
 | |
| 		MTP_inputDialogPeerFolder(MTP_int(folder->id())));
 | |
| 	session().api().request(MTPmessages_GetPeerDialogs(
 | |
| 		MTP_vector(std::move(peers))
 | |
| 	)).done([=](const MTPmessages_PeerDialogs &result) {
 | |
| 		applyPeerDialogs(result);
 | |
| 		_dialogFolderRequests.remove(folder);
 | |
| 	}).fail([=] {
 | |
| 		_dialogFolderRequests.remove(folder);
 | |
| 	}).send();
 | |
| }
 | |
| 
 | |
| void Histories::requestDialogEntry(
 | |
| 		not_null<History*> history,
 | |
| 		Fn<void()> callback) {
 | |
| 	const auto i = _dialogRequests.find(history);
 | |
| 	if (i != end(_dialogRequests)) {
 | |
| 		if (callback) {
 | |
| 			i->second.push_back(std::move(callback));
 | |
| 		}
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	const auto [j, ok] = _dialogRequestsPending.try_emplace(history);
 | |
| 	if (callback) {
 | |
| 		j->second.push_back(std::move(callback));
 | |
| 	}
 | |
| 	if (!ok) {
 | |
| 		return;
 | |
| 	}
 | |
| 	postponeRequestDialogEntries();
 | |
| }
 | |
| 
 | |
| void Histories::postponeRequestDialogEntries() {
 | |
| 	if (_dialogRequestsPending.size() > 1) {
 | |
| 		return;
 | |
| 	}
 | |
| 	Core::App().postponeCall(crl::guard(&session(), [=] {
 | |
| 		sendDialogRequests();
 | |
| 	}));
 | |
| }
 | |
| 
 | |
| void Histories::sendDialogRequests() {
 | |
| 	if (_dialogRequestsPending.empty()) {
 | |
| 		return;
 | |
| 	}
 | |
| 	const auto histories = ranges::views::all(
 | |
| 		_dialogRequestsPending
 | |
| 	) | ranges::views::transform([](const auto &pair) {
 | |
| 		return pair.first;
 | |
| 	}) | ranges::views::filter([&](not_null<History*> history) {
 | |
| 		const auto state = lookup(history);
 | |
| 		if (!state) {
 | |
| 			return true;
 | |
| 		} else if (!postponeEntryRequest(*state)) {
 | |
| 			return true;
 | |
| 		}
 | |
| 		state->postponedRequestEntry = true;
 | |
| 		return false;
 | |
| 	}) | ranges::to_vector;
 | |
| 
 | |
| 	auto peers = QVector<MTPInputDialogPeer>();
 | |
| 	const auto dialogPeer = [](not_null<History*> history) {
 | |
| 		return MTP_inputDialogPeer(history->peer->input);
 | |
| 	};
 | |
| 	ranges::transform(
 | |
| 		histories,
 | |
| 		ranges::back_inserter(peers),
 | |
| 		dialogPeer);
 | |
| 	for (auto &[history, callbacks] : base::take(_dialogRequestsPending)) {
 | |
| 		_dialogRequests.emplace(history, std::move(callbacks));
 | |
| 	}
 | |
| 
 | |
| 	const auto finalize = [=] {
 | |
| 		for (const auto &history : histories) {
 | |
| 			const auto state = lookup(history);
 | |
| 			if (!state || !state->postponedRequestEntry) {
 | |
| 				dialogEntryApplied(history);
 | |
| 				history->updateChatListExistence();
 | |
| 			}
 | |
| 		}
 | |
| 	};
 | |
| 	session().api().request(MTPmessages_GetPeerDialogs(
 | |
| 		MTP_vector(std::move(peers))
 | |
| 	)).done([=](const MTPmessages_PeerDialogs &result) {
 | |
| 		applyPeerDialogs(result);
 | |
| 		finalize();
 | |
| 	}).fail([=] {
 | |
| 		finalize();
 | |
| 	}).send();
 | |
| }
 | |
| 
 | |
| void Histories::dialogEntryApplied(not_null<History*> history) {
 | |
| 	const auto state = lookup(history);
 | |
| 	if (state && state->postponedRequestEntry) {
 | |
| 		return;
 | |
| 	}
 | |
| 	history->dialogEntryApplied();
 | |
| 	if (const auto callbacks = _dialogRequestsPending.take(history)) {
 | |
| 		for (const auto &callback : *callbacks) {
 | |
| 			callback();
 | |
| 		}
 | |
| 	}
 | |
| 	if (const auto callbacks = _dialogRequests.take(history)) {
 | |
| 		for (const auto &callback : *callbacks) {
 | |
| 			callback();
 | |
| 		}
 | |
| 	}
 | |
| 	if (state && state->sentReadTill && state->sentReadDone) {
 | |
| 		history->setInboxReadTill(base::take(state->sentReadTill));
 | |
| 		checkEmptyState(history);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void Histories::applyPeerDialogs(const MTPmessages_PeerDialogs &dialogs) {
 | |
| 	Expects(dialogs.type() == mtpc_messages_peerDialogs);
 | |
| 
 | |
| 	const auto &data = dialogs.c_messages_peerDialogs();
 | |
| 	_owner->processUsers(data.vusers());
 | |
| 	_owner->processChats(data.vchats());
 | |
| 	_owner->processMessages(data.vmessages(), NewMessageType::Last);
 | |
| 	for (const auto &dialog : data.vdialogs().v) {
 | |
| 		dialog.match([&](const MTPDdialog &data) {
 | |
| 			if (const auto peerId = peerFromMTP(data.vpeer())) {
 | |
| 				_owner->history(peerId)->applyDialog(nullptr, data);
 | |
| 			}
 | |
| 		}, [&](const MTPDdialogFolder &data) {
 | |
| 			const auto folder = _owner->processFolder(data.vfolder());
 | |
| 			folder->applyDialog(data);
 | |
| 		});
 | |
| 	}
 | |
| 	_owner->sendHistoryChangeNotifications();
 | |
| }
 | |
| 
 | |
| void Histories::changeDialogUnreadMark(
 | |
| 		not_null<History*> history,
 | |
| 		bool unread) {
 | |
| 	history->setUnreadMark(unread);
 | |
| 
 | |
| 	using Flag = MTPmessages_MarkDialogUnread::Flag;
 | |
| 	session().api().request(MTPmessages_MarkDialogUnread(
 | |
| 		MTP_flags(unread ? Flag::f_unread : Flag(0)),
 | |
| 		MTP_inputDialogPeer(history->peer->input)
 | |
| 	)).send();
 | |
| }
 | |
| 
 | |
| void Histories::requestFakeChatListMessage(
 | |
| 		not_null<History*> history) {
 | |
| 	if (_fakeChatListRequests.contains(history)) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	_fakeChatListRequests.emplace(history);
 | |
| 	sendRequest(history, RequestType::History, [=](Fn<void()> finish) {
 | |
| 		return session().api().request(MTPmessages_GetHistory(
 | |
| 			history->peer->input,
 | |
| 			MTP_int(0), // offset_id
 | |
| 			MTP_int(0), // offset_date
 | |
| 			MTP_int(0), // add_offset
 | |
| 			MTP_int(2), // limit
 | |
| 			MTP_int(0), // max_id
 | |
| 			MTP_int(0), // min_id
 | |
| 			MTP_long(0) // hash
 | |
| 		)).done([=](const MTPmessages_Messages &result) {
 | |
| 			_fakeChatListRequests.erase(history);
 | |
| 			history->setFakeChatListMessageFrom(result);
 | |
| 			finish();
 | |
| 		}).fail([=] {
 | |
| 			_fakeChatListRequests.erase(history);
 | |
| 			history->setFakeChatListMessageFrom(MTP_messages_messages(
 | |
| 				MTP_vector<MTPMessage>(0),
 | |
| 				MTP_vector<MTPChat>(0),
 | |
| 				MTP_vector<MTPUser>(0)));
 | |
| 			finish();
 | |
| 		}).send();
 | |
| 	});
 | |
| }
 | |
| 
 | |
| void Histories::requestGroupAround(not_null<HistoryItem*> item) {
 | |
| 	const auto history = item->history();
 | |
| 	const auto id = item->id;
 | |
| 	const auto key = GroupRequestKey{ history, item->topicRootId() };
 | |
| 	const auto i = _chatListGroupRequests.find(key);
 | |
| 	if (i != end(_chatListGroupRequests)) {
 | |
| 		if (i->second.aroundId == id) {
 | |
| 			return;
 | |
| 		} else {
 | |
| 			cancelRequest(i->second.requestId);
 | |
| 			_chatListGroupRequests.erase(i);
 | |
| 		}
 | |
| 	}
 | |
| 	constexpr auto kMaxAlbumCount = 10;
 | |
| 	const auto requestId = sendRequest(history, RequestType::History, [=](
 | |
| 			Fn<void()> finish) {
 | |
| 		return session().api().request(MTPmessages_GetHistory(
 | |
| 			history->peer->input,
 | |
| 			MTP_int(id),
 | |
| 			MTP_int(0), // offset_date
 | |
| 			MTP_int(-kMaxAlbumCount),
 | |
| 			MTP_int(2 * kMaxAlbumCount - 1),
 | |
| 			MTP_int(0), // max_id
 | |
| 			MTP_int(0), // min_id
 | |
| 			MTP_long(0) // hash
 | |
| 		)).done([=](const MTPmessages_Messages &result) {
 | |
| 			_owner->processExistingMessages(
 | |
| 				history->peer->asChannel(),
 | |
| 				result);
 | |
| 			_chatListGroupRequests.remove(key);
 | |
| 			history->migrateToOrMe()->applyChatListGroup(
 | |
| 				history->peer->id,
 | |
| 				result);
 | |
| 			finish();
 | |
| 		}).fail([=] {
 | |
| 			_chatListGroupRequests.remove(key);
 | |
| 			finish();
 | |
| 		}).send();
 | |
| 	});
 | |
| 	_chatListGroupRequests.emplace(
 | |
| 		key,
 | |
| 		ChatListGroupRequest{ .aroundId = id, .requestId = requestId });
 | |
| }
 | |
| 
 | |
| void Histories::sendPendingReadInbox(not_null<History*> history) {
 | |
| 	if (const auto state = lookup(history)) {
 | |
| 		DEBUG_LOG(("Reading: send pending now with till %1 and when %2"
 | |
| 			).arg(state->willReadTill.bare
 | |
| 			).arg(state->willReadWhen));
 | |
| 		if (state->willReadTill && state->willReadWhen) {
 | |
| 			state->willReadWhen = 0;
 | |
| 			sendReadRequests();
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void Histories::sendReadRequests() {
 | |
| 	DEBUG_LOG(("Reading: send requests with count %1.").arg(_states.size()));
 | |
| 	if (_states.empty()) {
 | |
| 		return;
 | |
| 	}
 | |
| 	const auto now = crl::now();
 | |
| 	auto next = std::optional<crl::time>();
 | |
| 	for (auto &[history, state] : _states) {
 | |
| 		if (!state.willReadTill) {
 | |
| 			DEBUG_LOG(("Reading: skipping zero till."));
 | |
| 			continue;
 | |
| 		} else if (state.willReadWhen <= now) {
 | |
| 			DEBUG_LOG(("Reading: sending with till %1."
 | |
| 				).arg(state.willReadTill.bare));
 | |
| 			sendReadRequest(history, state);
 | |
| 		} else if (!next || *next > state.willReadWhen) {
 | |
| 			DEBUG_LOG(("Reading: scheduling for later send."));
 | |
| 			next = state.willReadWhen;
 | |
| 		}
 | |
| 	}
 | |
| 	if (next.has_value()) {
 | |
| 		_readRequestsTimer.callOnce(*next - now);
 | |
| 	} else {
 | |
| 		_readRequestsTimer.cancel();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void Histories::sendReadRequest(not_null<History*> history, State &state) {
 | |
| 	Expects(state.willReadTill > state.sentReadTill);
 | |
| 
 | |
| 	const auto tillId = state.sentReadTill = base::take(state.willReadTill);
 | |
| 	state.willReadWhen = 0;
 | |
| 	state.sentReadDone = false;
 | |
| 	DEBUG_LOG(("Reading: sending request now with till %1."
 | |
| 		).arg(tillId.bare));
 | |
| 	sendRequest(history, RequestType::ReadInbox, [=](Fn<void()> finish) {
 | |
| 		DEBUG_LOG(("Reading: sending request invoked with till %1."
 | |
| 			).arg(tillId.bare));
 | |
| 		const auto finished = [=] {
 | |
| 			const auto state = lookup(history);
 | |
| 			Assert(state != nullptr);
 | |
| 
 | |
| 			if (state->sentReadTill == tillId) {
 | |
| 				state->sentReadDone = true;
 | |
| 				if (history->unreadCountRefreshNeeded(tillId)) {
 | |
| 					requestDialogEntry(history);
 | |
| 				} else {
 | |
| 					state->sentReadTill = 0;
 | |
| 				}
 | |
| 			} else {
 | |
| 				Assert(!state->sentReadTill || state->sentReadTill > tillId);
 | |
| 			}
 | |
| 			sendReadRequests();
 | |
| 			finish();
 | |
| 		};
 | |
| 		if (const auto channel = history->peer->asChannel()) {
 | |
| 			return session().api().request(MTPchannels_ReadHistory(
 | |
| 				channel->inputChannel,
 | |
| 				MTP_int(tillId)
 | |
| 			)).done(finished).fail(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([=] {
 | |
| 				finished();
 | |
| 			}).send();
 | |
| 		}
 | |
| 	});
 | |
| }
 | |
| 
 | |
| void Histories::checkEmptyState(not_null<History*> history) {
 | |
| 	const auto empty = [](const State &state) {
 | |
| 		return state.postponed.empty()
 | |
| 			&& !state.postponedRequestEntry
 | |
| 			&& state.sent.empty()
 | |
| 			&& (state.willReadTill == 0)
 | |
| 			&& (state.sentReadTill == 0);
 | |
| 	};
 | |
| 	const auto i = _states.find(history);
 | |
| 	if (i != end(_states) && empty(i->second)) {
 | |
| 		_states.erase(i);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool Histories::postponeHistoryRequest(const State &state) const {
 | |
| 	const auto proj = [](const auto &pair) {
 | |
| 		return pair.second.type;
 | |
| 	};
 | |
| 	const auto i = ranges::find(state.sent, RequestType::Delete, proj);
 | |
| 	return (i != end(state.sent));
 | |
| }
 | |
| 
 | |
| bool Histories::postponeEntryRequest(const State &state) const {
 | |
| 	return ranges::any_of(state.sent, [](const auto &pair) {
 | |
| 		return pair.second.type != RequestType::History;
 | |
| 	});
 | |
| }
 | |
| 
 | |
| void Histories::deleteMessages(
 | |
| 		not_null<History*> history,
 | |
| 		const QVector<MTPint> &ids,
 | |
| 		bool revoke) {
 | |
| 	sendRequest(history, RequestType::Delete, [=](Fn<void()> finish) {
 | |
| 		const auto done = [=](const MTPmessages_AffectedMessages &result) {
 | |
| 			session().api().applyAffectedMessages(history->peer, result);
 | |
| 			finish();
 | |
| 			history->requestChatListMessage();
 | |
| 		};
 | |
| 		if (const auto channel = history->peer->asChannel()) {
 | |
| 			return session().api().request(MTPchannels_DeleteMessages(
 | |
| 				channel->inputChannel,
 | |
| 				MTP_vector<MTPint>(ids)
 | |
| 			)).done(done).fail(finish).send();
 | |
| 		} else {
 | |
| 			using Flag = MTPmessages_DeleteMessages::Flag;
 | |
| 			return session().api().request(MTPmessages_DeleteMessages(
 | |
| 				MTP_flags(revoke ? Flag::f_revoke : Flag(0)),
 | |
| 				MTP_vector<MTPint>(ids)
 | |
| 			)).done(done).fail(finish).send();
 | |
| 		}
 | |
| 	});
 | |
| }
 | |
| 
 | |
| void Histories::deleteAllMessages(
 | |
| 		not_null<History*> history,
 | |
| 		MsgId deleteTillId,
 | |
| 		bool justClear,
 | |
| 		bool revoke) {
 | |
| 	sendRequest(history, RequestType::Delete, [=](Fn<void()> finish) {
 | |
| 		const auto peer = history->peer;
 | |
| 		const auto chat = peer->asChat();
 | |
| 		const auto channel = peer->asChannel();
 | |
| 		if (!justClear && revoke && channel && channel->canDelete()) {
 | |
| 			return session().api().request(MTPchannels_DeleteChannel(
 | |
| 				channel->inputChannel
 | |
| 			)).done([=](const MTPUpdates &result) {
 | |
| 				session().api().applyUpdates(result);
 | |
| 			//}).fail([=](const MTP::Error &error) {
 | |
| 			//	if (error.type() == u"CHANNEL_TOO_LARGE"_q) {
 | |
| 			//		Ui::show(Box<Ui::InformBox>(tr::lng_cant_delete_channel(tr::now)));
 | |
| 			//	}
 | |
| 			}).send();
 | |
| 		} else if (channel) {
 | |
| 			using Flag = MTPchannels_DeleteHistory::Flag;
 | |
| 			return session().api().request(MTPchannels_DeleteHistory(
 | |
| 				MTP_flags(revoke ? Flag::f_for_everyone : Flag(0)),
 | |
| 				channel->inputChannel,
 | |
| 				MTP_int(deleteTillId)
 | |
| 			)).done(finish).fail(finish).send();
 | |
| 		} else if (revoke && chat && chat->amCreator()) {
 | |
| 			return session().api().request(MTPmessages_DeleteChat(
 | |
| 				chat->inputChat
 | |
| 			)).done(finish).fail([=](const MTP::Error &error) {
 | |
| 				if (error.type() == "PEER_ID_INVALID") {
 | |
| 					// Try to join and delete,
 | |
| 					// while delete fails for non-joined.
 | |
| 					session().api().request(MTPmessages_AddChatUser(
 | |
| 						chat->inputChat,
 | |
| 						MTP_inputUserSelf(),
 | |
| 						MTP_int(0)
 | |
| 					)).done([=](const MTPUpdates &updates) {
 | |
| 						session().api().applyUpdates(updates);
 | |
| 						deleteAllMessages(
 | |
| 							history,
 | |
| 							deleteTillId,
 | |
| 							justClear,
 | |
| 							revoke);
 | |
| 					}).send();
 | |
| 				}
 | |
| 				finish();
 | |
| 			}).send();
 | |
| 		} else {
 | |
| 			using Flag = MTPmessages_DeleteHistory::Flag;
 | |
| 			const auto flags = Flag(0)
 | |
| 				| (justClear ? Flag::f_just_clear : Flag(0))
 | |
| 				| (revoke ? Flag::f_revoke : Flag(0));
 | |
| 			return session().api().request(MTPmessages_DeleteHistory(
 | |
| 				MTP_flags(flags),
 | |
| 				peer->input,
 | |
| 				MTP_int(0),
 | |
| 				MTPint(), // min_date
 | |
| 				MTPint() // max_date
 | |
| 			)).done([=](const MTPmessages_AffectedHistory &result) {
 | |
| 				const auto offset = session().api().applyAffectedHistory(
 | |
| 					peer,
 | |
| 					result);
 | |
| 				if (offset > 0) {
 | |
| 					deleteAllMessages(
 | |
| 						history,
 | |
| 						deleteTillId,
 | |
| 						justClear,
 | |
| 						revoke);
 | |
| 				}
 | |
| 				finish();
 | |
| 			}).fail(finish).send();
 | |
| 		}
 | |
| 	});
 | |
| }
 | |
| 
 | |
| void Histories::deleteMessagesByDates(
 | |
| 		not_null<History*> history,
 | |
| 		QDate firstDayToDelete,
 | |
| 		QDate lastDayToDelete,
 | |
| 		bool revoke) {
 | |
| 	const auto firstSecondToDelete = base::unixtime::serialize(
 | |
| 		{ firstDayToDelete, QTime(0, 0) }
 | |
| 	);
 | |
| 	const auto lastSecondToDelete = base::unixtime::serialize(
 | |
| 		{ lastDayToDelete, QTime(23, 59, 59) }
 | |
| 	);
 | |
| 	deleteMessagesByDates(
 | |
| 		history,
 | |
| 		firstSecondToDelete - 1,
 | |
| 		lastSecondToDelete + 1,
 | |
| 		revoke);
 | |
| }
 | |
| 
 | |
| void Histories::deleteMessagesByDates(
 | |
| 		not_null<History*> history,
 | |
| 		TimeId minDate,
 | |
| 		TimeId maxDate,
 | |
| 		bool revoke) {
 | |
| 	sendRequest(history, RequestType::Delete, [=](Fn<void()> finish) {
 | |
| 		const auto peer = history->peer;
 | |
| 		using Flag = MTPmessages_DeleteHistory::Flag;
 | |
| 		const auto flags = Flag::f_just_clear
 | |
| 			| Flag::f_min_date
 | |
| 			| Flag::f_max_date
 | |
| 			| (revoke ? Flag::f_revoke : Flag(0));
 | |
| 		return session().api().request(MTPmessages_DeleteHistory(
 | |
| 			MTP_flags(flags),
 | |
| 			peer->input,
 | |
| 			MTP_int(0),
 | |
| 			MTP_int(minDate),
 | |
| 			MTP_int(maxDate)
 | |
| 		)).done([=](const MTPmessages_AffectedHistory &result) {
 | |
| 			const auto offset = session().api().applyAffectedHistory(
 | |
| 				peer,
 | |
| 				result);
 | |
| 			if (offset > 0) {
 | |
| 				deleteMessagesByDates(history, minDate, maxDate, revoke);
 | |
| 			}
 | |
| 			finish();
 | |
| 		}).fail(finish).send();
 | |
| 	});
 | |
| 	history->destroyMessagesByDates(minDate, maxDate);
 | |
| }
 | |
| 
 | |
| void Histories::deleteMessages(const MessageIdsList &ids, bool revoke) {
 | |
| 	auto remove = std::vector<not_null<HistoryItem*>>();
 | |
| 	remove.reserve(ids.size());
 | |
| 	base::flat_map<not_null<History*>, QVector<MTPint>> idsByPeer;
 | |
| 	base::flat_map<not_null<PeerData*>, QVector<MTPint>> scheduledIdsByPeer;
 | |
| 	for (const auto &itemId : ids) {
 | |
| 		if (const auto item = _owner->message(itemId)) {
 | |
| 			const auto history = item->history();
 | |
| 			if (item->isScheduled()) {
 | |
| 				const auto wasOnServer = !item->isSending()
 | |
| 					&& !item->hasFailed();
 | |
| 				if (wasOnServer) {
 | |
| 					scheduledIdsByPeer[history->peer].push_back(MTP_int(
 | |
| 						_owner->scheduledMessages().lookupId(item)));
 | |
| 				} else {
 | |
| 					_owner->scheduledMessages().removeSending(item);
 | |
| 				}
 | |
| 				continue;
 | |
| 			}
 | |
| 			remove.push_back(item);
 | |
| 			if (item->isRegular()) {
 | |
| 				idsByPeer[history].push_back(MTP_int(itemId.msg));
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for (const auto &[history, ids] : idsByPeer) {
 | |
| 		history->owner().histories().deleteMessages(history, ids, revoke);
 | |
| 	}
 | |
| 	for (const auto &[peer, ids] : scheduledIdsByPeer) {
 | |
| 		peer->session().api().request(MTPmessages_DeleteScheduledMessages(
 | |
| 			peer->input,
 | |
| 			MTP_vector<MTPint>(ids)
 | |
| 		)).done([peer = peer](const MTPUpdates &result) {
 | |
| 			peer->session().api().applyUpdates(result);
 | |
| 		}).send();
 | |
| 	}
 | |
| 
 | |
| 	for (const auto item : remove) {
 | |
| 		const auto history = item->history();
 | |
| 		const auto wasLast = (history->lastMessage() == item);
 | |
| 		const auto wasInChats = (history->chatListMessage() == item);
 | |
| 		item->destroy();
 | |
| 
 | |
| 		if (wasLast || wasInChats) {
 | |
| 			history->requestChatListMessage();
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| int Histories::sendRequest(
 | |
| 		not_null<History*> history,
 | |
| 		RequestType type,
 | |
| 		Fn<mtpRequestId(Fn<void()> finish)> generator) {
 | |
| 	Expects(type != RequestType::None);
 | |
| 
 | |
| 	auto &state = _states[history];
 | |
| 	const auto id = ++_requestAutoincrement;
 | |
| 	_historyByRequest.emplace(id, history);
 | |
| 	if (type == RequestType::History && postponeHistoryRequest(state)) {
 | |
| 		state.postponed.emplace(
 | |
| 			id,
 | |
| 			PostponedHistoryRequest{ std::move(generator) });
 | |
| 		return id;
 | |
| 	}
 | |
| 	const auto requestId = generator([=] { checkPostponed(history, id); });
 | |
| 	state.sent.emplace(id, SentRequest{
 | |
| 		std::move(generator),
 | |
| 		requestId,
 | |
| 		type
 | |
| 	});
 | |
| 	if (!state.postponedRequestEntry
 | |
| 		&& postponeEntryRequest(state)
 | |
| 		&& _dialogRequests.contains(history)) {
 | |
| 		state.postponedRequestEntry = true;
 | |
| 	}
 | |
| 	if (postponeHistoryRequest(state)) {
 | |
| 		const auto resendHistoryRequest = [&](auto &pair) {
 | |
| 			auto &[id, sent] = pair;
 | |
| 			if (sent.type != RequestType::History) {
 | |
| 				return false;
 | |
| 			}
 | |
| 			state.postponed.emplace(
 | |
| 				id,
 | |
| 				PostponedHistoryRequest{ std::move(sent.generator) });
 | |
| 			session().api().request(sent.id).cancel();
 | |
| 			return true;
 | |
| 		};
 | |
| 		state.sent.erase(
 | |
| 			ranges::remove_if(state.sent, resendHistoryRequest),
 | |
| 			end(state.sent));
 | |
| 	}
 | |
| 	return id;
 | |
| }
 | |
| 
 | |
| void Histories::sendCreateTopicRequest(
 | |
| 		not_null<History*> history,
 | |
| 		MsgId rootId) {
 | |
| 	Expects(history->peer->isChannel());
 | |
| 
 | |
| 	const auto forum = history->asForum();
 | |
| 	Assert(forum != nullptr);
 | |
| 	const auto topic = forum->topicFor(rootId);
 | |
| 	Assert(topic != nullptr);
 | |
| 	const auto randomId = base::RandomValue<uint64>();
 | |
| 	session().data().registerMessageRandomId(
 | |
| 		randomId,
 | |
| 		{ history->peer->id, rootId });
 | |
| 	const auto api = &session().api();
 | |
| 	using Flag = MTPchannels_CreateForumTopic::Flag;
 | |
| 	api->request(MTPchannels_CreateForumTopic(
 | |
| 		MTP_flags(Flag::f_icon_color
 | |
| 			| (topic->iconId() ? Flag::f_icon_emoji_id : Flag(0))),
 | |
| 		history->peer->asChannel()->inputChannel,
 | |
| 		MTP_string(topic->title()),
 | |
| 		MTP_int(topic->colorId()),
 | |
| 		MTP_long(topic->iconId()),
 | |
| 		MTP_long(randomId),
 | |
| 		MTPInputPeer() // send_as
 | |
| 	)).done([=](const MTPUpdates &result) {
 | |
| 		api->applyUpdates(result, randomId);
 | |
| 	}).fail([=](const MTP::Error &error) {
 | |
| 		api->sendMessageFail(error, history->peer, randomId);
 | |
| 	}).send();
 | |
| }
 | |
| 
 | |
| bool Histories::isCreatingTopic(
 | |
| 		not_null<History*> history,
 | |
| 		MsgId rootId) const {
 | |
| 	const auto forum = history->asForum();
 | |
| 	return forum && forum->creating(rootId);
 | |
| }
 | |
| 
 | |
| int Histories::sendPreparedMessage(
 | |
| 		not_null<History*> history,
 | |
| 		FullReplyTo replyTo,
 | |
| 		uint64 randomId,
 | |
| 		Fn<PreparedMessage(not_null<History*>, FullReplyTo)> message,
 | |
| 		Fn<void(const MTPUpdates&, const MTP::Response&)> done,
 | |
| 		Fn<void(const MTP::Error&, const MTP::Response&)> fail) {
 | |
| 	if (isCreatingTopic(history, replyTo.topicRootId)) {
 | |
| 		const auto id = ++_requestAutoincrement;
 | |
| 		const auto creatingId = FullMsgId(
 | |
| 			history->peer->id,
 | |
| 			replyTo.topicRootId);
 | |
| 		auto i = _creatingTopics.find(creatingId);
 | |
| 		if (i == end(_creatingTopics)) {
 | |
| 			sendCreateTopicRequest(history, replyTo.topicRootId);
 | |
| 			i = _creatingTopics.emplace(creatingId).first;
 | |
| 		}
 | |
| 		i->second.push_back({
 | |
| 			.randomId = randomId,
 | |
| 			.replyTo = replyTo.messageId,
 | |
| 			.message = std::move(message),
 | |
| 			.done = std::move(done),
 | |
| 			.fail = std::move(fail),
 | |
| 			.requestId = id,
 | |
| 		});
 | |
| 		_creatingTopicRequests.emplace(id);
 | |
| 		return id;
 | |
| 	}
 | |
| 	const auto realReplyTo = FullReplyTo{
 | |
| 		.messageId = convertTopicReplyToId(history, replyTo.messageId),
 | |
| 		.quote = replyTo.quote,
 | |
| 		.storyId = replyTo.storyId,
 | |
| 		.topicRootId = convertTopicReplyToId(history, replyTo.topicRootId),
 | |
| 	};
 | |
| 	return v::match(message(history, realReplyTo), [&](const auto &request) {
 | |
| 		const auto type = RequestType::Send;
 | |
| 		return sendRequest(history, type, [=](Fn<void()> finish) {
 | |
| 			const auto session = &_owner->session();
 | |
| 			const auto api = &session->api();
 | |
| 			history->sendRequestId = api->request(
 | |
| 				base::duplicate(request)
 | |
| 			).done([=](
 | |
| 					const MTPUpdates &result,
 | |
| 					const MTP::Response &response) {
 | |
| 				api->applyUpdates(result, randomId);
 | |
| 				done(result, response);
 | |
| 				finish();
 | |
| 			}).fail([=](
 | |
| 					const MTP::Error &error,
 | |
| 					const MTP::Response &response) {
 | |
| 				fail(error, response);
 | |
| 				finish();
 | |
| 			}).afterRequest(
 | |
| 				history->sendRequestId
 | |
| 			).send();
 | |
| 			return history->sendRequestId;
 | |
| 		});
 | |
| 	});
 | |
| }
 | |
| 
 | |
| void Histories::checkTopicCreated(FullMsgId rootId, MsgId realRoot) {
 | |
| 	const auto i = _creatingTopics.find(rootId);
 | |
| 	if (i != end(_creatingTopics)) {
 | |
| 		auto scheduled = base::take(i->second);
 | |
| 		_creatingTopics.erase(i);
 | |
| 
 | |
| 		_createdTopicIds.emplace(rootId, realRoot);
 | |
| 
 | |
| 		const auto history = _owner->history(rootId.peer);
 | |
| 		if (const auto forum = history->asForum()) {
 | |
| 			forum->created(rootId.msg, realRoot);
 | |
| 		}
 | |
| 
 | |
| 		for (auto &entry : scheduled) {
 | |
| 			_creatingTopicRequests.erase(entry.requestId);
 | |
| 			sendPreparedMessage(
 | |
| 				history,
 | |
| 				FullReplyTo{
 | |
| 					.messageId = entry.replyTo,
 | |
| 					.topicRootId = realRoot,
 | |
| 				},
 | |
| 				entry.randomId,
 | |
| 				std::move(entry.message),
 | |
| 				std::move(entry.done),
 | |
| 				std::move(entry.fail));
 | |
| 		}
 | |
| 		for (const auto &item : history->clientSideMessages()) {
 | |
| 			const auto replace = [&](MsgId nowId) {
 | |
| 				return (nowId == rootId.msg) ? realRoot : nowId;
 | |
| 			};
 | |
| 			if (item->topicRootId() == rootId.msg) {
 | |
| 				item->setReplyFields(
 | |
| 					replace(item->replyToId()),
 | |
| 					realRoot,
 | |
| 					true);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| FullMsgId Histories::convertTopicReplyToId(
 | |
| 		not_null<History*> history,
 | |
| 		FullMsgId replyToId) const {
 | |
| 	const auto id = (history->peer->id == replyToId.peer)
 | |
| 		? convertTopicReplyToId(history, replyToId.msg)
 | |
| 		: replyToId.msg;
 | |
| 	return { replyToId.peer, id };
 | |
| }
 | |
| 
 | |
| MsgId Histories::convertTopicReplyToId(
 | |
| 		not_null<History*> history,
 | |
| 		MsgId replyToId) const {
 | |
| 	if (!replyToId) {
 | |
| 		return {};
 | |
| 	}
 | |
| 	const auto i = _createdTopicIds.find({ history->peer->id, replyToId });
 | |
| 	return (i != end(_createdTopicIds)) ? i->second : replyToId;
 | |
| }
 | |
| 
 | |
| void Histories::checkPostponed(not_null<History*> history, int id) {
 | |
| 	if (const auto state = lookup(history)) {
 | |
| 		finishSentRequest(history, state, id);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void Histories::cancelRequest(int id) {
 | |
| 	if (!id) {
 | |
| 		return;
 | |
| 	} else if (_creatingTopicRequests.contains(id)) {
 | |
| 		cancelDelayedByTopicRequest(id);
 | |
| 		return;
 | |
| 	}
 | |
| 	const auto history = _historyByRequest.take(id);
 | |
| 	if (!history) {
 | |
| 		return;
 | |
| 	}
 | |
| 	const auto state = lookup(*history);
 | |
| 	if (!state) {
 | |
| 		return;
 | |
| 	}
 | |
| 	state->postponed.remove(id);
 | |
| 	finishSentRequest(*history, state, id);
 | |
| }
 | |
| 
 | |
| void Histories::cancelDelayedByTopicRequest(int id) {
 | |
| 	for (auto &[rootId, messages] : _creatingTopics) {
 | |
| 		messages.erase(
 | |
| 			ranges::remove(messages, id, &DelayedByTopicMessage::requestId),
 | |
| 			end(messages));
 | |
| 	}
 | |
| 	_creatingTopicRequests.remove(id);
 | |
| }
 | |
| 
 | |
| void Histories::finishSentRequest(
 | |
| 		not_null<History*> history,
 | |
| 		not_null<State*> state,
 | |
| 		int id) {
 | |
| 	_historyByRequest.remove(id);
 | |
| 	const auto i = state->sent.find(id);
 | |
| 	if (i != end(state->sent)) {
 | |
| 		session().api().request(i->second.id).cancel();
 | |
| 		state->sent.erase(i);
 | |
| 	}
 | |
| 	if (!state->postponed.empty() && !postponeHistoryRequest(*state)) {
 | |
| 		for (auto &[id, postponed] : base::take(state->postponed)) {
 | |
| 			const auto requestId = postponed.generator([=, id=id] {
 | |
| 				checkPostponed(history, id);
 | |
| 			});
 | |
| 			state->sent.emplace(id, SentRequest{
 | |
| 				std::move(postponed.generator),
 | |
| 				requestId,
 | |
| 				RequestType::History
 | |
| 			});
 | |
| 		}
 | |
| 	}
 | |
| 	if (state->postponedRequestEntry && !postponeEntryRequest(*state)) {
 | |
| 		const auto i = _dialogRequests.find(history);
 | |
| 		Assert(i != end(_dialogRequests));
 | |
| 		const auto [j, ok] = _dialogRequestsPending.emplace(
 | |
| 			history,
 | |
| 			std::move(i->second));
 | |
| 		Assert(ok);
 | |
| 		_dialogRequests.erase(i);
 | |
| 		state->postponedRequestEntry = false;
 | |
| 		postponeRequestDialogEntries();
 | |
| 	}
 | |
| 	checkEmptyState(history);
 | |
| }
 | |
| 
 | |
| Histories::State *Histories::lookup(not_null<History*> history) {
 | |
| 	const auto i = _states.find(history);
 | |
| 	return (i != end(_states)) ? &i->second : nullptr;
 | |
| }
 | |
| 
 | |
| } // namespace Data
 | 
