diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index ea058fc40..d071feae1 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -167,6 +167,8 @@ PRIVATE api/api_single_message_search.h api/api_text_entities.cpp api/api_text_entities.h + api/api_updates.cpp + api/api_updates.h boxes/filters/edit_filter_box.cpp boxes/filters/edit_filter_box.h boxes/filters/edit_filter_chats_list.cpp diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp new file mode 100644 index 000000000..f66bac02b --- /dev/null +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -0,0 +1,2030 @@ +/* +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 "api/api_updates.h" + +#include "api/api_text_entities.h" +#include "main/main_session.h" +#include "main/main_account.h" +#include "mtproto/mtp_instance.h" +#include "mtproto/dc_options.h" +#include "data/stickers/data_stickers.h" +#include "data/data_session.h" +#include "data/data_user.h" +#include "data/data_chat.h" +#include "data/data_channel.h" +#include "data/data_chat_filters.h" +#include "data/data_cloud_themes.h" +#include "data/data_drafts.h" +#include "data/data_histories.h" +#include "data/data_folder.h" +#include "data/data_scheduled_messages.h" +#include "lang/lang_cloud_manager.h" +#include "history/history.h" +#include "history/history_item.h" +#include "core/application.h" +#include "storage/storage_account.h" +#include "storage/storage_facade.h" +#include "storage/storage_user_photos.h" +#include "storage/storage_shared_media.h" +#include "calls/calls_instance.h" +#include "base/unixtime.h" +#include "window/window_session_controller.h" +#include "window/window_controller.h" +#include "boxes/confirm_box.h" +#include "apiwrap.h" +#include "observer_peer.h" +#include "app.h" // App::formatPhone +#include "facades.h" + +namespace Api { +namespace { + +constexpr auto kChannelGetDifferenceLimit = 100; + +// 1s wait after show channel history before sending getChannelDifference. +constexpr auto kWaitForChannelGetDifference = crl::time(1000); + +// If nothing is received in 1 min we ping. +constexpr auto kNoUpdatesTimeout = 60 * 1000; + +// If nothing is received in 1 min when was a sleepmode we ping. +constexpr auto kNoUpdatesAfterSleepTimeout = 60 * crl::time(1000); + +enum class DataIsLoadedResult { + NotLoaded = 0, + FromNotLoaded = 1, + MentionNotLoaded = 2, + Ok = 3, +}; + +bool IsForceLogoutNotification(const MTPDupdateServiceNotification &data) { + return qs(data.vtype()).startsWith(qstr("AUTH_KEY_DROP_")); +} + +bool HasForceLogoutNotification(const MTPUpdates &updates) { + const auto checkUpdate = [](const MTPUpdate &update) { + if (update.type() != mtpc_updateServiceNotification) { + return false; + } + return IsForceLogoutNotification( + update.c_updateServiceNotification()); + }; + const auto checkVector = [&](const MTPVector &list) { + for (const auto &update : list.v) { + if (checkUpdate(update)) { + return true; + } + } + return false; + }; + switch (updates.type()) { + case mtpc_updates: + return checkVector(updates.c_updates().vupdates()); + case mtpc_updatesCombined: + return checkVector(updates.c_updatesCombined().vupdates()); + case mtpc_updateShort: + return checkUpdate(updates.c_updateShort().vupdate()); + } + return false; +} + +bool ForwardedInfoDataLoaded( + not_null session, + const MTPMessageFwdHeader &header) { + return header.match([&](const MTPDmessageFwdHeader &data) { + if (const auto channelId = data.vchannel_id()) { + if (!session->data().channelLoaded(channelId->v)) { + return false; + } + if (const auto fromId = data.vfrom_id()) { + const auto from = session->data().user(fromId->v); + // Minimal loaded is fine in this case. + if (from->loadedStatus == PeerData::NotLoaded) { + return false; + } + } + } else if (const auto fromId = data.vfrom_id()) { + // Fully loaded is required in this case. + if (!session->data().userLoaded(fromId->v)) { + return false; + } + } + return true; + }); +} + +bool MentionUsersLoaded( + not_null session, + const MTPVector &entities) { + for (const auto &entity : entities.v) { + auto type = entity.type(); + if (type == mtpc_messageEntityMentionName) { + if (!session->data().userLoaded(entity.c_messageEntityMentionName().vuser_id().v)) { + return false; + } + } else if (type == mtpc_inputMessageEntityMentionName) { + auto &inputUser = entity.c_inputMessageEntityMentionName().vuser_id(); + if (inputUser.type() == mtpc_inputUser) { + if (!session->data().userLoaded(inputUser.c_inputUser().vuser_id().v)) { + return false; + } + } + } + } + return true; +} + +DataIsLoadedResult AllDataLoadedForMessage( + not_null session, + const MTPMessage &message) { + return message.match([&](const MTPDmessage &message) { + if (const auto fromId = message.vfrom_id()) { + if (!message.is_post() + && !session->data().userLoaded(fromId->v)) { + return DataIsLoadedResult::FromNotLoaded; + } + } + if (const auto viaBotId = message.vvia_bot_id()) { + if (!session->data().userLoaded(viaBotId->v)) { + return DataIsLoadedResult::NotLoaded; + } + } + if (const auto fwd = message.vfwd_from()) { + if (!ForwardedInfoDataLoaded(session, *fwd)) { + return DataIsLoadedResult::NotLoaded; + } + } + if (const auto entities = message.ventities()) { + if (!MentionUsersLoaded(session, *entities)) { + return DataIsLoadedResult::MentionNotLoaded; + } + } + return DataIsLoadedResult::Ok; + }, [&](const MTPDmessageService &message) { + if (const auto fromId = message.vfrom_id()) { + if (!message.is_post() + && !session->data().userLoaded(fromId->v)) { + return DataIsLoadedResult::FromNotLoaded; + } + } + return message.vaction().match( + [&](const MTPDmessageActionChatAddUser &action) { + for (const MTPint &userId : action.vusers().v) { + if (!session->data().userLoaded(userId.v)) { + return DataIsLoadedResult::NotLoaded; + } + } + return DataIsLoadedResult::Ok; + }, [&](const MTPDmessageActionChatJoinedByLink &action) { + if (!session->data().userLoaded(action.vinviter_id().v)) { + return DataIsLoadedResult::NotLoaded; + } + return DataIsLoadedResult::Ok; + }, [&](const MTPDmessageActionChatDeleteUser &action) { + if (!session->data().userLoaded(action.vuser_id().v)) { + return DataIsLoadedResult::NotLoaded; + } + return DataIsLoadedResult::Ok; + }, [](const auto &) { + return DataIsLoadedResult::Ok; + }); + }, [](const MTPDmessageEmpty &message) { + return DataIsLoadedResult::Ok; + }); +} + +} // namespace + +Updates::Updates(not_null session) +: _session(session) +, _noUpdatesTimer([=] { sendPing(); }) +, _onlineTimer([=] { updateOnline(); }) +, _ptsWaiter(this) +, _byPtsTimer([=] { getDifferenceByPts(); }) +, _bySeqTimer([=] { getDifference(); }) +, _byMinChannelTimer([=] { getDifference(); }) +, _failDifferenceTimer([=] { getDifferenceAfterFail(); }) +, _idleFinishTimer([=] { checkIdleFinish(); }) { + _ptsWaiter.setRequesting(true); + + session->account().mtpUpdates( + ) | rpl::start_with_next([=](const MTPUpdates &updates) { + mtpUpdateReceived(updates); + }, _lifetime); + + session->account().mtpNewSessionCreated( + ) | rpl::start_with_next([=] { + mtpNewSessionCreated(); + }, _lifetime); + + api().request(MTPupdates_GetState( + )).done([=](const MTPupdates_State &result) { + }).send(); +} + +Main::Session &Updates::session() const { + return *_session; +} + +ApiWrap &Updates::api() const { + return _session->api(); +} + +void Updates::checkLastUpdate(bool afterSleep) { + const auto now = crl::now(); + const auto skip = afterSleep + ? kNoUpdatesAfterSleepTimeout + : kNoUpdatesTimeout; + if (_lastUpdateTime && now > _lastUpdateTime + skip) { + _lastUpdateTime = now; + sendPing(); + } +} + +void Updates::feedUpdateVector( + const MTPVector &updates, + bool skipMessageIds) { + for (const auto &update : updates.v) { + if (skipMessageIds && update.type() == mtpc_updateMessageID) { + continue; + } + feedUpdate(update); + } + session().data().sendHistoryChangeNotifications(); +} + +void Updates::feedMessageIds(const MTPVector &updates) { + for (const auto &update : updates.v) { + if (update.type() == mtpc_updateMessageID) { + feedUpdate(update); + } + } +} + +void Updates::setState(int32 pts, int32 date, int32 qts, int32 seq) { + if (pts) { + _ptsWaiter.init(pts); + } + if (_updatesDate < date && !_byMinChannelTimer.isActive()) { + _updatesDate = date; + } + if (qts && _updatesQts < qts) { + _updatesQts = qts; + } + if (seq && seq != _updatesSeq) { + _updatesSeq = seq; + if (_bySeqTimer.isActive()) { + _bySeqTimer.cancel(); + } + while (!_bySeqUpdates.empty()) { + const auto s = _bySeqUpdates.front().first; + if (s <= seq + 1) { + const auto v = _bySeqUpdates.front().second; + _bySeqUpdates.erase(_bySeqUpdates.begin()); + if (s == seq + 1) { + return applyUpdates(v); + } + } else { + if (!_bySeqTimer.isActive()) { + _bySeqTimer.callOnce(PtsWaiter::kWaitForSkippedTimeout); + } + break; + } + } + } +} + +void Updates::channelDifferenceDone( + not_null channel, + const MTPupdates_ChannelDifference &difference) { + _channelFailDifferenceTimeout.remove(channel); + + const auto timeout = difference.match([&](const auto &data) { + return data.vtimeout().value_or_empty(); + }); + const auto isFinal = difference.match([&](const auto &data) { + return data.is_final(); + }); + difference.match([&](const MTPDupdates_channelDifferenceEmpty &data) { + channel->ptsInit(data.vpts().v); + }, [&](const MTPDupdates_channelDifferenceTooLong &data) { + session().data().processUsers(data.vusers()); + session().data().processChats(data.vchats()); + const auto history = session().data().historyLoaded(channel->id); + if (history) { + history->setNotLoadedAtBottom(); + requestChannelRangeDifference(history); + } + data.vdialog().match([&](const MTPDdialog &data) { + if (const auto pts = data.vpts()) { + channel->ptsInit(pts->v); + } + }, [&](const MTPDdialogFolder &) { + }); + session().data().applyDialogs( + nullptr, + data.vmessages().v, + QVector(1, data.vdialog())); + session().data().channelDifferenceTooLong(channel); + }, [&](const MTPDupdates_channelDifference &data) { + feedChannelDifference(data); + channel->ptsInit(data.vpts().v); + }); + + channel->ptsSetRequesting(false); + + if (!isFinal) { + MTP_LOG(0, ("getChannelDifference { good - after not final channelDifference was received }%1").arg(cTestMode() ? " TESTMODE" : "")); + getChannelDifference(channel); + } else if (ranges::contains( + _activeChats, + channel, + [](const auto &pair) { return pair.second.peer; })) { + channel->ptsWaitingForShortPoll(timeout + ? (timeout * crl::time(1000)) + : kWaitForChannelGetDifference); + } +} + +void Updates::feedChannelDifference( + const MTPDupdates_channelDifference &data) { + session().data().processUsers(data.vusers()); + session().data().processChats(data.vchats()); + + _handlingChannelDifference = true; + feedMessageIds(data.vother_updates()); + session().data().processMessages( + data.vnew_messages(), + NewMessageType::Unread); + feedUpdateVector(data.vother_updates(), true); + _handlingChannelDifference = false; +} + +void Updates::channelDifferenceFail( + not_null channel, + const RPCError &error) { + LOG(("RPC Error in getChannelDifference: %1 %2: %3" + ).arg(error.code() + ).arg(error.type() + ).arg(error.description())); + failDifferenceStartTimerFor(channel); +} + +void Updates::stateDone(const MTPupdates_State &state) { + const auto &d = state.c_updates_state(); + setState(d.vpts().v, d.vdate().v, d.vqts().v, d.vseq().v); + + _lastUpdateTime = crl::now(); + _noUpdatesTimer.callOnce(kNoUpdatesTimeout); + _ptsWaiter.setRequesting(false); + + session().api().requestDialogs(); + updateOnline(); +} + +void Updates::differenceDone(const MTPupdates_Difference &result) { + _failDifferenceTimeout = 1; + + switch (result.type()) { + case mtpc_updates_differenceEmpty: { + auto &d = result.c_updates_differenceEmpty(); + setState(_ptsWaiter.current(), d.vdate().v, _updatesQts, d.vseq().v); + + _lastUpdateTime = crl::now(); + _noUpdatesTimer.callOnce(kNoUpdatesTimeout); + + _ptsWaiter.setRequesting(false); + } break; + case mtpc_updates_differenceSlice: { + auto &d = result.c_updates_differenceSlice(); + feedDifference(d.vusers(), d.vchats(), d.vnew_messages(), d.vother_updates()); + + auto &s = d.vintermediate_state().c_updates_state(); + setState(s.vpts().v, s.vdate().v, s.vqts().v, s.vseq().v); + + _ptsWaiter.setRequesting(false); + + MTP_LOG(0, ("getDifference { good - after a slice of difference was received }%1").arg(cTestMode() ? " TESTMODE" : "")); + getDifference(); + } break; + case mtpc_updates_difference: { + auto &d = result.c_updates_difference(); + feedDifference(d.vusers(), d.vchats(), d.vnew_messages(), d.vother_updates()); + + stateDone(d.vstate()); + } break; + case mtpc_updates_differenceTooLong: { + auto &d = result.c_updates_differenceTooLong(); + LOG(("API Error: updates.differenceTooLong is not supported by Telegram Desktop!")); + } break; + }; +} + +bool Updates::whenGetDiffChanged( + ChannelData *channel, + int32 ms, + base::flat_map, crl::time> &whenMap, + crl::time &curTime) { + if (channel) { + if (ms <= 0) { + const auto i = whenMap.find(channel); + if (i != whenMap.cend()) { + whenMap.erase(i); + } else { + return false; + } + } else { + auto when = crl::now() + ms; + const auto i = whenMap.find(channel); + if (i != whenMap.cend()) { + if (i->second > when) { + i->second = when; + } else { + return false; + } + } else { + whenMap.emplace(channel, when); + } + } + } else { + if (ms <= 0) { + if (curTime) { + curTime = 0; + } else { + return false; + } + } else { + auto when = crl::now() + ms; + if (!curTime || curTime > when) { + curTime = when; + } else { + return false; + } + } + } + return true; +} + +void Updates::ptsWaiterStartTimerFor(ChannelData *channel, crl::time ms) { + if (whenGetDiffChanged(channel, ms, _whenGetDiffByPts, _getDifferenceTimeByPts)) { + getDifferenceByPts(); + } +} + +void Updates::failDifferenceStartTimerFor(ChannelData *channel) { + auto &timeout = [&]() -> crl::time & { + if (!channel) { + return _failDifferenceTimeout; + } + const auto i = _channelFailDifferenceTimeout.find(channel); + return (i == _channelFailDifferenceTimeout.end()) + ? _channelFailDifferenceTimeout.emplace(channel, 1).first->second + : i->second; + }(); + if (whenGetDiffChanged(channel, timeout * 1000, _whenGetDiffAfterFail, _getDifferenceTimeAfterFail)) { + getDifferenceAfterFail(); + } + if (timeout < 64) timeout *= 2; +} + +bool Updates::updateAndApply( + int32 pts, + int32 ptsCount, + const MTPUpdates &updates) { + return _ptsWaiter.updateAndApply(nullptr, pts, ptsCount, updates); +} + +bool Updates::updateAndApply( + int32 pts, + int32 ptsCount, + const MTPUpdate &update) { + return _ptsWaiter.updateAndApply(nullptr, pts, ptsCount, update); +} + +bool Updates::updateAndApply(int32 pts, int32 ptsCount) { + return _ptsWaiter.updateAndApply(nullptr, pts, ptsCount); +} + +void Updates::feedDifference( + const MTPVector &users, + const MTPVector &chats, + const MTPVector &msgs, + const MTPVector &other) { + Core::App().checkAutoLock(); + session().data().processUsers(users); + session().data().processChats(chats); + feedMessageIds(other); + session().data().processMessages(msgs, NewMessageType::Unread); + feedUpdateVector(other, true); +} + +void Updates::differenceFail(const RPCError &error) { + LOG(("RPC Error in getDifference: %1 %2: %3" + ).arg(error.code() + ).arg(error.type() + ).arg(error.description())); + failDifferenceStartTimerFor(nullptr); +} + +void Updates::getDifferenceByPts() { + auto now = crl::now(), wait = crl::time(0); + if (_getDifferenceTimeByPts) { + if (_getDifferenceTimeByPts > now) { + wait = _getDifferenceTimeByPts - now; + } else { + getDifference(); + } + } + for (auto i = _whenGetDiffByPts.begin(); i != _whenGetDiffByPts.cend();) { + if (i->second > now) { + wait = wait ? std::min(wait, i->second - now) : (i->second - now); + ++i; + } else { + getChannelDifference( + i->first, + ChannelDifferenceRequest::PtsGapOrShortPoll); + i = _whenGetDiffByPts.erase(i); + } + } + if (wait) { + _byPtsTimer.callOnce(wait); + } else { + _byPtsTimer.cancel(); + } +} + +void Updates::getDifferenceAfterFail() { + auto now = crl::now(), wait = crl::time(0); + if (_getDifferenceTimeAfterFail) { + if (_getDifferenceTimeAfterFail > now) { + wait = _getDifferenceTimeAfterFail - now; + } else { + _ptsWaiter.setRequesting(false); + MTP_LOG(0, ("getDifference { force - after get difference failed }%1").arg(cTestMode() ? " TESTMODE" : "")); + getDifference(); + } + } + for (auto i = _whenGetDiffAfterFail.begin(); i != _whenGetDiffAfterFail.cend();) { + if (i->second > now) { + wait = wait ? std::min(wait, i->second - now) : (i->second - now); + ++i; + } else { + getChannelDifference(i->first, ChannelDifferenceRequest::AfterFail); + i = _whenGetDiffAfterFail.erase(i); + } + } + if (wait) { + _failDifferenceTimer.callOnce(wait); + } else { + _failDifferenceTimer.cancel(); + } +} + +void Updates::getDifference() { + _getDifferenceTimeByPts = 0; + + if (requestingDifference()) { + return; + } + + _bySeqUpdates.clear(); + _bySeqTimer.cancel(); + + _noUpdatesTimer.cancel(); + _getDifferenceTimeAfterFail = 0; + + _ptsWaiter.setRequesting(true); + + api().request(MTPupdates_GetDifference( + MTP_flags(0), + MTP_int(_ptsWaiter.current()), + MTPint(), + MTP_int(_updatesDate), + MTP_int(_updatesQts) + )).done([=](const MTPupdates_Difference &result) { + differenceDone(result); + }).fail([=](const RPCError &error) { + differenceFail(error); + }).send(); +} + +void Updates::getChannelDifference( + not_null channel, + ChannelDifferenceRequest from) { + if (from != ChannelDifferenceRequest::PtsGapOrShortPoll) { + _whenGetDiffByPts.remove(channel); + } + + if (!channel->ptsInited() || channel->ptsRequesting()) return; + + if (from != ChannelDifferenceRequest::AfterFail) { + _whenGetDiffAfterFail.remove(channel); + } + + channel->ptsSetRequesting(true); + + auto filter = MTP_channelMessagesFilterEmpty(); + auto flags = MTPupdates_GetChannelDifference::Flag::f_force | 0; + if (from != ChannelDifferenceRequest::PtsGapOrShortPoll) { + if (!channel->ptsWaitingForSkipped()) { + flags = 0; // No force flag when requesting for short poll. + } + } + api().request(MTPupdates_GetChannelDifference( + MTP_flags(flags), + channel->inputChannel, + filter, + MTP_int(channel->pts()), + MTP_int(kChannelGetDifferenceLimit) + )).done([=](const MTPupdates_ChannelDifference &result) { + channelDifferenceDone(channel, result); + }).fail([=](const RPCError &error) { + channelDifferenceFail(channel, error); + }).send(); +} + +void Updates::sendPing() { + api().instance()->ping(); +} + +void Updates::addActiveChat(rpl::producer chat) { + const auto key = _activeChats.empty() ? 0 : _activeChats.back().first + 1; + std::move( + chat + ) | rpl::start_with_next_done([=](PeerData *peer) { + _activeChats[key].peer = peer; + if (const auto channel = peer ? peer->asChannel() : nullptr) { + channel->ptsWaitingForShortPoll( + kWaitForChannelGetDifference); + } + }, [=] { + _activeChats.erase(key); + }, _activeChats[key].lifetime); +} + +void Updates::requestChannelRangeDifference(not_null history) { + Expects(history->isChannel()); + + const auto channel = history->peer->asChannel(); + if (const auto requestId = _rangeDifferenceRequests.take(channel)) { + api().request(*requestId).cancel(); + } + const auto range = history->rangeForDifferenceRequest(); + if (!(range.from < range.till) || !channel->pts()) { + return; + } + + MTP_LOG(0, ("getChannelDifference { good - " + "after channelDifferenceTooLong was received, " + "validating history part }%1").arg(cTestMode() ? " TESTMODE" : "")); + channelRangeDifferenceSend(channel, range, channel->pts()); +} + +void Updates::channelRangeDifferenceSend( + not_null channel, + MsgRange range, + int32 pts) { + Expects(range.from < range.till); + + const auto limit = range.till - range.from; + const auto filter = MTP_channelMessagesFilter( + MTP_flags(0), + MTP_vector(1, MTP_messageRange( + MTP_int(range.from), + MTP_int(range.till - 1)))); + const auto requestId = api().request(MTPupdates_GetChannelDifference( + MTP_flags(MTPupdates_GetChannelDifference::Flag::f_force), + channel->inputChannel, + filter, + MTP_int(pts), + MTP_int(limit) + )).done([=](const MTPupdates_ChannelDifference &result) { + _rangeDifferenceRequests.remove(channel); + channelRangeDifferenceDone(channel, range, result); + }).fail([=](const RPCError &error) { + _rangeDifferenceRequests.remove(channel); + }).send(); + _rangeDifferenceRequests.emplace(channel, requestId); +} + +void Updates::channelRangeDifferenceDone( + not_null channel, + MsgRange range, + const MTPupdates_ChannelDifference &result) { + auto nextRequestPts = int32(0); + auto isFinal = true; + + switch (result.type()) { + case mtpc_updates_channelDifferenceEmpty: { + const auto &d = result.c_updates_channelDifferenceEmpty(); + nextRequestPts = d.vpts().v; + isFinal = d.is_final(); + } break; + + case mtpc_updates_channelDifferenceTooLong: { + const auto &d = result.c_updates_channelDifferenceTooLong(); + + _session->data().processUsers(d.vusers()); + _session->data().processChats(d.vchats()); + + nextRequestPts = d.vdialog().match([&](const MTPDdialog &data) { + return data.vpts().value_or_empty(); + }, [&](const MTPDdialogFolder &data) { + return 0; + }); + isFinal = d.is_final(); + } break; + + case mtpc_updates_channelDifference: { + const auto &d = result.c_updates_channelDifference(); + + feedChannelDifference(d); + + nextRequestPts = d.vpts().v; + isFinal = d.is_final(); + } break; + } + + if (!isFinal && nextRequestPts) { + MTP_LOG(0, ("getChannelDifference { " + "good - after not final channelDifference was received, " + "validating history part }%1" + ).arg(cTestMode() ? " TESTMODE" : "")); + channelRangeDifferenceSend(channel, range, nextRequestPts); + } +} + +void Updates::mtpNewSessionCreated() { + Core::App().checkAutoLock(); + _updatesSeq = 0; + MTP_LOG(0, ("getDifference { after new_session_created }%1" + ).arg(cTestMode() ? " TESTMODE" : "")); + getDifference(); +} + +void Updates::mtpUpdateReceived(const MTPUpdates &updates) { + Core::App().checkAutoLock(); + _lastUpdateTime = crl::now(); + _noUpdatesTimer.callOnce(kNoUpdatesTimeout); + if (!requestingDifference() + || HasForceLogoutNotification(updates)) { + applyUpdates(updates); + } +} + +void Updates::updateOnline() { + updateOnline(false); +} + +bool Updates::isIdle() const { + return _isIdle; +} + +void Updates::updateOnline(bool gotOtherOffline) { + crl::on_main(&session(), [] { Core::App().checkAutoLock(); }); + + bool isOnline = Core::App().hasActiveWindow(&session()); + int updateIn = Global::OnlineUpdatePeriod(); + Assert(updateIn >= 0); + if (isOnline) { + const auto idle = crl::now() - Core::App().lastNonIdleTime(); + if (idle >= Global::OfflineIdleTimeout()) { + isOnline = false; + if (!_isIdle) { + _isIdle = true; + _idleFinishTimer.callOnce(900); + } + } else { + updateIn = qMin(updateIn, int(Global::OfflineIdleTimeout() - idle)); + Assert(updateIn >= 0); + } + } + auto ms = crl::now(); + if (isOnline != _lastWasOnline + || (isOnline && _lastSetOnline + Global::OnlineUpdatePeriod() <= ms) + || (isOnline && gotOtherOffline)) { + api().request(base::take(_onlineRequest)).cancel(); + + _lastWasOnline = isOnline; + _lastSetOnline = ms; + if (!App::quitting()) { + _onlineRequest = api().request(MTPaccount_UpdateStatus( + MTP_bool(!isOnline) + )).send(); + } else { + _onlineRequest = api().request(MTPaccount_UpdateStatus( + MTP_bool(!isOnline) + )).done([=](const MTPBool &result) { + Core::App().quitPreventFinished(); + }).fail([=](const RPCError &error) { + Core::App().quitPreventFinished(); + }).send(); + } + + const auto self = session().user(); + self->onlineTill = base::unixtime::now() + (isOnline ? (Global::OnlineUpdatePeriod() / 1000) : -1); + Notify::peerUpdatedDelayed( + self, + Notify::PeerUpdate::Flag::UserOnlineChanged); + if (!isOnline) { // Went offline, so we need to save message draft to the cloud. + api().saveCurrentDraftToCloud(); + } + + _lastSetOnline = ms; + } else if (isOnline) { + updateIn = qMin(updateIn, int(_lastSetOnline + Global::OnlineUpdatePeriod() - ms)); + Assert(updateIn >= 0); + } + _onlineTimer.callOnce(updateIn); +} + +void Updates::checkIdleFinish() { + if (crl::now() - Core::App().lastNonIdleTime() + < Global::OfflineIdleTimeout()) { + _idleFinishTimer.cancel(); + _isIdle = false; + updateOnline(); + App::wnd()->checkHistoryActivation(); + } else { + _idleFinishTimer.callOnce(900); + } +} + +bool Updates::lastWasOnline() const { + return _lastWasOnline; +} + +crl::time Updates::lastSetOnline() const { + return _lastSetOnline; +} + +bool Updates::isQuitPrevent() { + if (!_lastWasOnline) { + return false; + } + LOG(("Api::Updates prevents quit, sending offline status...")); + updateOnline(); + return true; +} + +void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) { + switch (updates.type()) { + case mtpc_updateShortMessage: { + const auto &d = updates.c_updateShortMessage(); + const auto flags = mtpCastFlags(d.vflags().v) + | MTPDmessage::Flag::f_from_id; + const auto peerUserId = d.is_out() + ? d.vuser_id() + : MTP_int(_session->userId()); + const auto fwd = d.vfwd_from(); + _session->data().addNewMessage( + MTP_message( + MTP_flags(flags), + d.vid(), + d.is_out() ? MTP_int(_session->userId()) : d.vuser_id(), + MTP_peerUser(peerUserId), + fwd ? (*fwd) : MTPMessageFwdHeader(), + MTP_int(d.vvia_bot_id().value_or_empty()), + MTP_int(d.vreply_to_msg_id().value_or_empty()), + d.vdate(), + d.vmessage(), + MTP_messageMediaEmpty(), + MTPReplyMarkup(), + MTP_vector(d.ventities().value_or_empty()), + MTPint(), + MTPint(), + MTPstring(), + MTPlong(), + //MTPMessageReactions(), + MTPVector()), + MTPDmessage_ClientFlags(), + NewMessageType::Unread); + } break; + + case mtpc_updateShortChatMessage: { + const auto &d = updates.c_updateShortChatMessage(); + const auto flags = mtpCastFlags(d.vflags().v) | MTPDmessage::Flag::f_from_id; + const auto fwd = d.vfwd_from(); + _session->data().addNewMessage( + MTP_message( + MTP_flags(flags), + d.vid(), + d.vfrom_id(), + MTP_peerChat(d.vchat_id()), + fwd ? (*fwd) : MTPMessageFwdHeader(), + MTP_int(d.vvia_bot_id().value_or_empty()), + MTP_int(d.vreply_to_msg_id().value_or_empty()), + d.vdate(), + d.vmessage(), + MTP_messageMediaEmpty(), + MTPReplyMarkup(), + MTP_vector(d.ventities().value_or_empty()), + MTPint(), + MTPint(), + MTPstring(), + MTPlong(), + //MTPMessageReactions(), + MTPVector()), + MTPDmessage_ClientFlags(), + NewMessageType::Unread); + } break; + + case mtpc_updateShortSentMessage: { + auto &d = updates.c_updateShortSentMessage(); + Q_UNUSED(d); // Sent message data was applied anyway. + } break; + + default: Unexpected("Type in applyUpdatesNoPtsCheck()"); + } +} + +void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) { + switch (update.type()) { + case mtpc_updateNewMessage: { + auto &d = update.c_updateNewMessage(); + auto needToAdd = true; + if (d.vmessage().type() == mtpc_message) { // index forwarded messages to links _overview + const auto &data = d.vmessage().c_message(); + if (_session->data().checkEntitiesAndViewsUpdate(data)) { // already in blocks + LOG(("Skipping message, because it is already in blocks!")); + needToAdd = false; + } + if (needToAdd && !data.is_from_scheduled()) { + // If we still need to add a new message, + // we should first check if this message is in + // the list of scheduled messages. + // This is necessary to correctly update the file reference. + // Note that when a message is scheduled until online + // while the recipient is already online, the server sends + // an ordinary new message with skipped "from_scheduled" flag. + _session->data().scheduledMessages().checkEntitiesAndUpdate( + data); + } + } + if (needToAdd) { + _session->data().addNewMessage( + d.vmessage(), + MTPDmessage_ClientFlags(), + NewMessageType::Unread); + } + } break; + + case mtpc_updateReadMessagesContents: { + const auto &d = update.c_updateReadMessagesContents(); + auto possiblyReadMentions = base::flat_set(); + for (const auto &msgId : d.vmessages().v) { + if (const auto item = _session->data().message(NoChannel, msgId.v)) { + if (item->isUnreadMedia() || item->isUnreadMention()) { + item->markMediaRead(); + _session->data().requestItemRepaint(item); + + if (item->out() + && item->history()->peer->isUser() + && !requestingDifference()) { + item->history()->peer->asUser()->madeAction(base::unixtime::now()); + } + } + } else { + // Perhaps it was an unread mention! + possiblyReadMentions.insert(msgId.v); + } + } + session().api().checkForUnreadMentions(possiblyReadMentions); + } break; + + case mtpc_updateReadHistoryInbox: { + const auto &d = update.c_updateReadHistoryInbox(); + const auto peer = peerFromMTP(d.vpeer()); + if (const auto history = _session->data().historyLoaded(peer)) { + const auto folderId = d.vfolder_id().value_or_empty(); + history->applyInboxReadUpdate( + folderId, + d.vmax_id().v, + d.vstill_unread_count().v); + } + } break; + + case mtpc_updateReadHistoryOutbox: { + const auto &d = update.c_updateReadHistoryOutbox(); + const auto peer = peerFromMTP(d.vpeer()); + if (const auto history = _session->data().historyLoaded(peer)) { + history->outboxRead(d.vmax_id().v); + if (!requestingDifference()) { + if (const auto user = history->peer->asUser()) { + user->madeAction(base::unixtime::now()); + } + } + } + } break; + + case mtpc_updateWebPage: { + auto &d = update.c_updateWebPage(); + Q_UNUSED(d); // Web page was updated anyway. + } break; + + case mtpc_updateFolderPeers: { + const auto &data = update.c_updateFolderPeers(); + auto &owner = _session->data(); + for (const auto &peer : data.vfolder_peers().v) { + peer.match([&](const MTPDfolderPeer &data) { + const auto peerId = peerFromMTP(data.vpeer()); + if (const auto history = owner.historyLoaded(peerId)) { + if (const auto folderId = data.vfolder_id().v) { + history->setFolder(owner.folder(folderId)); + } else { + history->clearFolder(); + } + } + }); + } + } break; + + case mtpc_updateDeleteMessages: { + auto &d = update.c_updateDeleteMessages(); + _session->data().processMessagesDeleted(NoChannel, d.vmessages().v); + } break; + + case mtpc_updateNewChannelMessage: { + auto &d = update.c_updateNewChannelMessage(); + auto needToAdd = true; + if (d.vmessage().type() == mtpc_message) { // index forwarded messages to links _overview + if (_session->data().checkEntitiesAndViewsUpdate(d.vmessage().c_message())) { // already in blocks + LOG(("Skipping message, because it is already in blocks!")); + needToAdd = false; + } + } + if (needToAdd) { + _session->data().addNewMessage( + d.vmessage(), + MTPDmessage_ClientFlags(), + NewMessageType::Unread); + } + } break; + + case mtpc_updateEditChannelMessage: { + auto &d = update.c_updateEditChannelMessage(); + _session->data().updateEditedMessage(d.vmessage()); + } break; + + case mtpc_updateEditMessage: { + auto &d = update.c_updateEditMessage(); + _session->data().updateEditedMessage(d.vmessage()); + } break; + + case mtpc_updateChannelWebPage: { + auto &d = update.c_updateChannelWebPage(); + Q_UNUSED(d); // Web page was updated anyway. + } break; + + case mtpc_updateDeleteChannelMessages: { + auto &d = update.c_updateDeleteChannelMessages(); + _session->data().processMessagesDeleted(d.vchannel_id().v, d.vmessages().v); + } break; + + default: Unexpected("Type in applyUpdateNoPtsCheck()"); + } +} + +void Updates::applyUpdates( + const MTPUpdates &updates, + uint64 sentMessageRandomId) { + const auto randomId = sentMessageRandomId; + + switch (updates.type()) { + case mtpc_updates: { + auto &d = updates.c_updates(); + if (d.vseq().v) { + if (d.vseq().v <= _updatesSeq) { + return; + } + if (d.vseq().v > _updatesSeq + 1) { + _bySeqUpdates.emplace(d.vseq().v, updates); + _bySeqTimer.callOnce(PtsWaiter::kWaitForSkippedTimeout); + return; + } + } + + session().data().processUsers(d.vusers()); + session().data().processChats(d.vchats()); + feedUpdateVector(d.vupdates()); + + setState(0, d.vdate().v, _updatesQts, d.vseq().v); + } break; + + case mtpc_updatesCombined: { + auto &d = updates.c_updatesCombined(); + if (d.vseq_start().v) { + if (d.vseq_start().v <= _updatesSeq) { + return; + } + if (d.vseq_start().v > _updatesSeq + 1) { + _bySeqUpdates.emplace(d.vseq_start().v, updates); + _bySeqTimer.callOnce(PtsWaiter::kWaitForSkippedTimeout); + return; + } + } + + session().data().processUsers(d.vusers()); + session().data().processChats(d.vchats()); + feedUpdateVector(d.vupdates()); + + setState(0, d.vdate().v, _updatesQts, d.vseq().v); + } break; + + case mtpc_updateShort: { + auto &d = updates.c_updateShort(); + feedUpdate(d.vupdate()); + + setState(0, d.vdate().v, _updatesQts, _updatesSeq); + } break; + + case mtpc_updateShortMessage: { + auto &d = updates.c_updateShortMessage(); + const auto viaBotId = d.vvia_bot_id(); + const auto entities = d.ventities(); + const auto fwd = d.vfwd_from(); + if (!session().data().userLoaded(d.vuser_id().v) + || (viaBotId && !session().data().userLoaded(viaBotId->v)) + || (entities && !MentionUsersLoaded(&session(), *entities)) + || (fwd && !ForwardedInfoDataLoaded(&session(), *fwd))) { + MTP_LOG(0, ("getDifference { good - getting user for updateShortMessage }%1").arg(cTestMode() ? " TESTMODE" : "")); + return getDifference(); + } + if (updateAndApply(d.vpts().v, d.vpts_count().v, updates)) { + // Update date as well. + setState(0, d.vdate().v, _updatesQts, _updatesSeq); + } + } break; + + case mtpc_updateShortChatMessage: { + auto &d = updates.c_updateShortChatMessage(); + const auto noFrom = !session().data().userLoaded(d.vfrom_id().v); + const auto chat = session().data().chatLoaded(d.vchat_id().v); + const auto viaBotId = d.vvia_bot_id(); + const auto entities = d.ventities(); + const auto fwd = d.vfwd_from(); + if (!chat + || noFrom + || (viaBotId && !session().data().userLoaded(viaBotId->v)) + || (entities && !MentionUsersLoaded(&session(), *entities)) + || (fwd && !ForwardedInfoDataLoaded(&session(), *fwd))) { + MTP_LOG(0, ("getDifference { good - getting user for updateShortChatMessage }%1").arg(cTestMode() ? " TESTMODE" : "")); + if (chat && noFrom) { + session().api().requestFullPeer(chat); + } + return getDifference(); + } + if (updateAndApply(d.vpts().v, d.vpts_count().v, updates)) { + // Update date as well. + setState(0, d.vdate().v, _updatesQts, _updatesSeq); + } + } break; + + case mtpc_updateShortSentMessage: { + auto &d = updates.c_updateShortSentMessage(); + if (!IsServerMsgId(d.vid().v)) { + LOG(("API Error: Bad msgId got from server: %1").arg(d.vid().v)); + } else if (randomId) { + auto &owner = session().data(); + const auto sent = owner.messageSentData(randomId); + const auto lookupMessage = [&] { + return sent.peerId + ? owner.message(peerToChannel(sent.peerId), d.vid().v) + : nullptr; + }; + if (const auto id = owner.messageIdByRandomId(randomId)) { + const auto local = owner.message(id); + if (local && local->isScheduled()) { + owner.scheduledMessages().sendNowSimpleMessage(d, local); + } + } + const auto wasAlready = (lookupMessage() != nullptr); + feedUpdate(MTP_updateMessageID(d.vid(), MTP_long(randomId))); // ignore real date + if (const auto item = lookupMessage()) { + const auto list = d.ventities(); + if (list && !MentionUsersLoaded(&session(), *list)) { + session().api().requestMessageData( + item->history()->peer->asChannel(), + item->id, + ApiWrap::RequestMessageDataCallback()); + } + item->updateSentContent({ + sent.text, + Api::EntitiesFromMTP(&session(), list.value_or_empty()) + }, d.vmedia()); + item->contributeToSlowmode(d.vdate().v); + if (!wasAlready) { + item->indexAsNewItem(); + } + } + } + + if (updateAndApply(d.vpts().v, d.vpts_count().v, updates)) { + // Update date as well. + setState(0, d.vdate().v, _updatesQts, _updatesSeq); + } + } break; + + case mtpc_updatesTooLong: { + MTP_LOG(0, ("getDifference { good - updatesTooLong received }%1").arg(cTestMode() ? " TESTMODE" : "")); + return getDifference(); + } break; + } + session().data().sendHistoryChangeNotifications(); +} + +void Updates::feedUpdate(const MTPUpdate &update) { + switch (update.type()) { + + // New messages. + case mtpc_updateNewMessage: { + auto &d = update.c_updateNewMessage(); + + const auto isDataLoaded = AllDataLoadedForMessage(&session(), d.vmessage()); + if (!requestingDifference() && isDataLoaded != DataIsLoadedResult::Ok) { + MTP_LOG(0, ("getDifference { good - " + "after not all data loaded in updateNewMessage }%1" + ).arg(cTestMode() ? " TESTMODE" : "")); + + // This can be if this update was created by grouping + // some short message update into an updates vector. + return getDifference(); + } + + updateAndApply(d.vpts().v, d.vpts_count().v, update); + } break; + + case mtpc_updateNewChannelMessage: { + auto &d = update.c_updateNewChannelMessage(); + auto channel = session().data().channelLoaded(peerToChannel(PeerFromMessage(d.vmessage()))); + const auto isDataLoaded = AllDataLoadedForMessage(&session(), d.vmessage()); + if (!requestingDifference() && (!channel || isDataLoaded != DataIsLoadedResult::Ok)) { + MTP_LOG(0, ("getDifference { good - " + "after not all data loaded in updateNewChannelMessage }%1" + ).arg(cTestMode() ? " TESTMODE" : "")); + + // Request last active supergroup participants if the 'from' user was not loaded yet. + // This will optimize similar getDifference() calls for almost all next messages. + if (isDataLoaded == DataIsLoadedResult::FromNotLoaded && channel && channel->isMegagroup()) { + if (channel->mgInfo->lastParticipants.size() < Global::ChatSizeMax() && (channel->mgInfo->lastParticipants.empty() || channel->mgInfo->lastParticipants.size() < channel->membersCount())) { + session().api().requestLastParticipants(channel); + } + } + + if (!_byMinChannelTimer.isActive()) { // getDifference after timeout + _byMinChannelTimer.callOnce(PtsWaiter::kWaitForSkippedTimeout); + } + return; + } + if (channel && !_handlingChannelDifference) { + if (channel->ptsRequesting()) { // skip global updates while getting channel difference + return; + } + channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update); + } else { + applyUpdateNoPtsCheck(update); + } + } break; + + case mtpc_updateMessageID: { + const auto &d = update.c_updateMessageID(); + const auto randomId = d.vrandom_id().v; + if (const auto id = session().data().messageIdByRandomId(randomId)) { + const auto newId = d.vid().v; + if (const auto local = session().data().message(id)) { + if (local->isScheduled()) { + session().data().scheduledMessages().apply(d, local); + } else { + const auto channel = id.channel; + const auto existing = session().data().message( + channel, + newId); + if (existing && !local->mainView()) { + const auto history = local->history(); + local->destroy(); + history->requestChatListMessage(); + } else { + if (existing) { + existing->destroy(); + } + local->setRealId(d.vid().v); + } + } + } + session().data().unregisterMessageRandomId(randomId); + } + session().data().unregisterMessageSentData(randomId); + } break; + + // Message contents being read. + case mtpc_updateReadMessagesContents: { + auto &d = update.c_updateReadMessagesContents(); + updateAndApply(d.vpts().v, d.vpts_count().v, update); + } break; + + case mtpc_updateChannelReadMessagesContents: { + auto &d = update.c_updateChannelReadMessagesContents(); + auto channel = session().data().channelLoaded(d.vchannel_id().v); + if (!channel) { + if (!_byMinChannelTimer.isActive()) { + // getDifference after timeout. + _byMinChannelTimer.callOnce(PtsWaiter::kWaitForSkippedTimeout); + } + return; + } + auto possiblyReadMentions = base::flat_set(); + for_const (auto &msgId, d.vmessages().v) { + if (auto item = session().data().message(channel, msgId.v)) { + if (item->isUnreadMedia() || item->isUnreadMention()) { + item->markMediaRead(); + session().data().requestItemRepaint(item); + } + } else { + // Perhaps it was an unread mention! + possiblyReadMentions.insert(msgId.v); + } + } + session().api().checkForUnreadMentions(possiblyReadMentions, channel); + } break; + + // Edited messages. + case mtpc_updateEditMessage: { + auto &d = update.c_updateEditMessage(); + updateAndApply(d.vpts().v, d.vpts_count().v, update); + } break; + + case mtpc_updateEditChannelMessage: { + auto &d = update.c_updateEditChannelMessage(); + auto channel = session().data().channelLoaded(peerToChannel(PeerFromMessage(d.vmessage()))); + + if (channel && !_handlingChannelDifference) { + if (channel->ptsRequesting()) { // skip global updates while getting channel difference + return; + } else { + channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update); + } + } else { + applyUpdateNoPtsCheck(update); + } + } break; + + // Messages being read. + case mtpc_updateReadHistoryInbox: { + auto &d = update.c_updateReadHistoryInbox(); + updateAndApply(d.vpts().v, d.vpts_count().v, update); + } break; + + case mtpc_updateReadHistoryOutbox: { + auto &d = update.c_updateReadHistoryOutbox(); + updateAndApply(d.vpts().v, d.vpts_count().v, update); + } break; + + case mtpc_updateReadChannelInbox: { + const auto &d = update.c_updateReadChannelInbox(); + const auto peer = peerFromChannel(d.vchannel_id().v); + if (const auto history = session().data().historyLoaded(peer)) { + history->applyInboxReadUpdate( + d.vfolder_id().value_or_empty(), + d.vmax_id().v, + d.vstill_unread_count().v, + d.vpts().v); + } + } break; + + case mtpc_updateReadChannelOutbox: { + const auto &d = update.c_updateReadChannelOutbox(); + const auto peer = peerFromChannel(d.vchannel_id().v); + if (const auto history = session().data().historyLoaded(peer)) { + history->outboxRead(d.vmax_id().v); + if (!requestingDifference()) { + if (const auto user = history->peer->asUser()) { + user->madeAction(base::unixtime::now()); + } + } + } + } break; + + //case mtpc_updateReadFeed: { // #feed + // const auto &d = update.c_updateReadFeed(); + // const auto feedId = d.vfeed_id().v; + // if (const auto feed = session().data().feedLoaded(feedId)) { + // feed->setUnreadPosition( + // Data::FeedPositionFromMTP(d.vmax_position())); + // if (d.vunread_count() && d.vunread_muted_count()) { + // feed->setUnreadCounts( + // d.vunread_count()->v, + // d.vunread_muted_count()->v); + // } else { + // session().data().histories().requestDialogEntry(feed); + // } + // } + //} break; + + case mtpc_updateDialogUnreadMark: { + const auto &data = update.c_updateDialogUnreadMark(); + data.vpeer().match( + [&](const MTPDdialogPeer &dialog) { + const auto id = peerFromMTP(dialog.vpeer()); + if (const auto history = session().data().historyLoaded(id)) { + history->setUnreadMark(data.is_unread()); + } + }, [&](const MTPDdialogPeerFolder &dialog) { + const auto id = dialog.vfolder_id().v; // #TODO archive + //if (const auto folder = session().data().folderLoaded(id)) { + // folder->setUnreadMark(data.is_unread()); + //} + }); + } break; + + case mtpc_updateFolderPeers: { + const auto &data = update.c_updateFolderPeers(); + + updateAndApply(data.vpts().v, data.vpts_count().v, update); + } break; + + case mtpc_updateDialogFilter: + case mtpc_updateDialogFilterOrder: + case mtpc_updateDialogFilters: { + session().data().chatsFilters().apply(update); + } break; + + // Deleted messages. + case mtpc_updateDeleteMessages: { + auto &d = update.c_updateDeleteMessages(); + + updateAndApply(d.vpts().v, d.vpts_count().v, update); + } break; + + case mtpc_updateDeleteChannelMessages: { + auto &d = update.c_updateDeleteChannelMessages(); + auto channel = session().data().channelLoaded(d.vchannel_id().v); + + if (channel && !_handlingChannelDifference) { + if (channel->ptsRequesting()) { // skip global updates while getting channel difference + return; + } + channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update); + } else { + applyUpdateNoPtsCheck(update); + } + } break; + + case mtpc_updateNewScheduledMessage: { + const auto &d = update.c_updateNewScheduledMessage(); + session().data().scheduledMessages().apply(d); + } break; + + case mtpc_updateDeleteScheduledMessages: { + const auto &d = update.c_updateDeleteScheduledMessages(); + session().data().scheduledMessages().apply(d); + } break; + + case mtpc_updateWebPage: { + auto &d = update.c_updateWebPage(); + + // Update web page anyway. + session().data().processWebpage(d.vwebpage()); + session().data().sendWebPageGamePollNotifications(); + + updateAndApply(d.vpts().v, d.vpts_count().v, update); + } break; + + case mtpc_updateChannelWebPage: { + auto &d = update.c_updateChannelWebPage(); + + // Update web page anyway. + session().data().processWebpage(d.vwebpage()); + session().data().sendWebPageGamePollNotifications(); + + auto channel = session().data().channelLoaded(d.vchannel_id().v); + if (channel && !_handlingChannelDifference) { + if (channel->ptsRequesting()) { // skip global updates while getting channel difference + return; + } else { + channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update); + } + } else { + applyUpdateNoPtsCheck(update); + } + } break; + + case mtpc_updateMessagePoll: { + session().data().applyUpdate(update.c_updateMessagePoll()); + } break; + + case mtpc_updateUserTyping: { + auto &d = update.c_updateUserTyping(); + const auto userId = peerFromUser(d.vuser_id()); + const auto history = session().data().historyLoaded(userId); + const auto user = session().data().userLoaded(d.vuser_id().v); + if (history && user) { + const auto when = requestingDifference() ? 0 : base::unixtime::now(); + session().data().registerSendAction(history, user, d.vaction(), when); + } + } break; + + case mtpc_updateChatUserTyping: { + auto &d = update.c_updateChatUserTyping(); + const auto history = [&]() -> History* { + if (const auto chat = session().data().chatLoaded(d.vchat_id().v)) { + return session().data().historyLoaded(chat->id); + } else if (const auto channel = session().data().channelLoaded(d.vchat_id().v)) { + return session().data().historyLoaded(channel->id); + } + return nullptr; + }(); + const auto user = (d.vuser_id().v == session().userId()) + ? nullptr + : session().data().userLoaded(d.vuser_id().v); + if (history && user) { + const auto when = requestingDifference() ? 0 : base::unixtime::now(); + session().data().registerSendAction(history, user, d.vaction(), when); + } + } break; + + case mtpc_updateChatParticipants: { + session().data().applyUpdate(update.c_updateChatParticipants()); + } break; + + case mtpc_updateChatParticipantAdd: { + session().data().applyUpdate(update.c_updateChatParticipantAdd()); + } break; + + case mtpc_updateChatParticipantDelete: { + session().data().applyUpdate(update.c_updateChatParticipantDelete()); + } break; + + case mtpc_updateChatParticipantAdmin: { + session().data().applyUpdate(update.c_updateChatParticipantAdmin()); + } break; + + case mtpc_updateChatDefaultBannedRights: { + session().data().applyUpdate(update.c_updateChatDefaultBannedRights()); + } break; + + case mtpc_updateUserStatus: { + auto &d = update.c_updateUserStatus(); + if (auto user = session().data().userLoaded(d.vuser_id().v)) { + switch (d.vstatus().type()) { + case mtpc_userStatusEmpty: user->onlineTill = 0; break; + case mtpc_userStatusRecently: + if (user->onlineTill > -10) { // don't modify pseudo-online + user->onlineTill = -2; + } + break; + case mtpc_userStatusLastWeek: user->onlineTill = -3; break; + case mtpc_userStatusLastMonth: user->onlineTill = -4; break; + case mtpc_userStatusOffline: user->onlineTill = d.vstatus().c_userStatusOffline().vwas_online().v; break; + case mtpc_userStatusOnline: user->onlineTill = d.vstatus().c_userStatusOnline().vexpires().v; break; + } + Notify::peerUpdatedDelayed( + user, + Notify::PeerUpdate::Flag::UserOnlineChanged); + } + if (d.vuser_id().v == session().userId()) { + if (d.vstatus().type() == mtpc_userStatusOffline + || d.vstatus().type() == mtpc_userStatusEmpty) { + updateOnline(true); + if (d.vstatus().type() == mtpc_userStatusOffline) { + cSetOtherOnline( + d.vstatus().c_userStatusOffline().vwas_online().v); + } + } else if (d.vstatus().type() == mtpc_userStatusOnline) { + cSetOtherOnline( + d.vstatus().c_userStatusOnline().vexpires().v); + } + } + } break; + + case mtpc_updateUserName: { + auto &d = update.c_updateUserName(); + if (auto user = session().data().userLoaded(d.vuser_id().v)) { + if (!user->isContact()) { + user->setName( + TextUtilities::SingleLine(qs(d.vfirst_name())), + TextUtilities::SingleLine(qs(d.vlast_name())), + user->nameOrPhone, + TextUtilities::SingleLine(qs(d.vusername()))); + } else { + user->setName( + TextUtilities::SingleLine(user->firstName), + TextUtilities::SingleLine(user->lastName), + user->nameOrPhone, + TextUtilities::SingleLine(qs(d.vusername()))); + } + } + } break; + + case mtpc_updateUserPhoto: { + auto &d = update.c_updateUserPhoto(); + if (auto user = session().data().userLoaded(d.vuser_id().v)) { + user->setPhoto(d.vphoto()); + user->loadUserpic(); + if (mtpIsTrue(d.vprevious()) || !user->userpicPhotoId()) { + session().storage().remove(Storage::UserPhotosRemoveAfter( + user->bareId(), + user->userpicPhotoId())); + } else { + session().storage().add(Storage::UserPhotosAddNew( + user->bareId(), + user->userpicPhotoId())); + } + } + } break; + + case mtpc_updatePeerSettings: { + const auto &d = update.c_updatePeerSettings(); + const auto peerId = peerFromMTP(d.vpeer()); + if (const auto peer = session().data().peerLoaded(peerId)) { + const auto settings = d.vsettings().match([]( + const MTPDpeerSettings &data) { + return data.vflags().v; + }); + peer->setSettings(settings); + } + } break; + + case mtpc_updateNotifySettings: { + auto &d = update.c_updateNotifySettings(); + session().data().applyNotifySetting(d.vpeer(), d.vnotify_settings()); + } break; + + case mtpc_updateDcOptions: { + auto &d = update.c_updateDcOptions(); + Core::App().dcOptions()->addFromList(d.vdc_options()); + } break; + + case mtpc_updateConfig: { + session().mtp()->requestConfig(); + } break; + + case mtpc_updateUserPhone: { + const auto &d = update.c_updateUserPhone(); + if (const auto user = session().data().userLoaded(d.vuser_id().v)) { + const auto newPhone = qs(d.vphone()); + if (newPhone != user->phone()) { + user->setPhone(newPhone); + user->setName( + user->firstName, + user->lastName, + ((user->isContact() + || user->isServiceUser() + || user->isSelf() + || user->phone().isEmpty()) + ? QString() + : App::formatPhone(user->phone())), + user->username); + + Notify::peerUpdatedDelayed( + user, + Notify::PeerUpdate::Flag::UserPhoneChanged); + } + } + } break; + + case mtpc_updateNewEncryptedMessage: { + auto &d = update.c_updateNewEncryptedMessage(); + } break; + + case mtpc_updateEncryptedChatTyping: { + auto &d = update.c_updateEncryptedChatTyping(); + } break; + + case mtpc_updateEncryption: { + auto &d = update.c_updateEncryption(); + } break; + + case mtpc_updateEncryptedMessagesRead: { + auto &d = update.c_updateEncryptedMessagesRead(); + } break; + + case mtpc_updatePhoneCall: { + session().calls().handleUpdate(update.c_updatePhoneCall()); + } break; + + case mtpc_updateUserBlocked: { + const auto &d = update.c_updateUserBlocked(); + if (const auto user = session().data().userLoaded(d.vuser_id().v)) { + user->setIsBlocked(mtpIsTrue(d.vblocked())); + } + } break; + + case mtpc_updateServiceNotification: { + const auto &d = update.c_updateServiceNotification(); + const auto text = TextWithEntities { + qs(d.vmessage()), + Api::EntitiesFromMTP(&session(), d.ventities().v) + }; + if (IsForceLogoutNotification(d)) { + Core::App().forceLogOut(text); + } else if (d.is_popup()) { + const auto &windows = session().windows(); + if (!windows.empty()) { + windows.front()->window().show(Box(text)); + } + } else { + session().data().serviceNotification(text, d.vmedia()); + session().data().checkNewAuthorization(); + } + } break; + + case mtpc_updatePrivacy: { + auto &d = update.c_updatePrivacy(); + const auto allChatsLoaded = [&](const MTPVector &ids) { + for (const auto &chatId : ids.v) { + if (!session().data().chatLoaded(chatId.v) + && !session().data().channelLoaded(chatId.v)) { + return false; + } + } + return true; + }; + const auto allLoaded = [&] { + for (const auto &rule : d.vrules().v) { + const auto loaded = rule.match([&]( + const MTPDprivacyValueAllowChatParticipants & data) { + return allChatsLoaded(data.vchats()); + }, [&](const MTPDprivacyValueDisallowChatParticipants & data) { + return allChatsLoaded(data.vchats()); + }, [](auto &&) { return true; }); + if (!loaded) { + return false; + } + } + return true; + }; + if (const auto key = ApiWrap::Privacy::KeyFromMTP(d.vkey().type())) { + if (allLoaded()) { + session().api().handlePrivacyChange(*key, d.vrules()); + } else { + session().api().reloadPrivacy(*key); + } + } + } break; + + case mtpc_updatePinnedDialogs: { + const auto &d = update.c_updatePinnedDialogs(); + const auto folderId = d.vfolder_id().value_or_empty(); + const auto loaded = !folderId + || (session().data().folderLoaded(folderId) != nullptr); + const auto folder = folderId + ? session().data().folder(folderId).get() + : nullptr; + const auto done = [&] { + const auto list = d.vorder(); + if (!list) { + return false; + } + const auto &order = list->v; + const auto notLoaded = [&](const MTPDialogPeer &peer) { + return peer.match([&](const MTPDdialogPeer &data) { + return !session().data().historyLoaded( + peerFromMTP(data.vpeer())); + }, [&](const MTPDdialogPeerFolder &data) { + if (folderId) { + LOG(("API Error: " + "updatePinnedDialogs has nested folders.")); + return true; + } + return !session().data().folderLoaded(data.vfolder_id().v); + }); + }; + const auto allLoaded = ranges::find_if(order, notLoaded) + == order.end(); + if (!allLoaded) { + return false; + } + session().data().applyPinnedChats(folder, order); + return true; + }(); + if (!done) { + session().api().requestPinnedDialogs(folder); + } + if (!loaded) { + session().data().histories().requestDialogEntry(folder); + } + } break; + + case mtpc_updateDialogPinned: { + const auto &d = update.c_updateDialogPinned(); + const auto folderId = d.vfolder_id().value_or_empty(); + const auto folder = folderId + ? session().data().folder(folderId).get() + : nullptr; + const auto done = d.vpeer().match([&](const MTPDdialogPeer &data) { + const auto id = peerFromMTP(data.vpeer()); + if (const auto history = session().data().historyLoaded(id)) { + history->applyPinnedUpdate(d); + return true; + } + DEBUG_LOG(("API Error: " + "pinned chat not loaded for peer %1, folder: %2" + ).arg(id + ).arg(folderId + )); + return false; + }, [&](const MTPDdialogPeerFolder &data) { + if (folderId != 0) { + DEBUG_LOG(("API Error: Nested folders updateDialogPinned.")); + return false; + } + const auto id = data.vfolder_id().v; + if (const auto folder = session().data().folderLoaded(id)) { + folder->applyPinnedUpdate(d); + return true; + } + DEBUG_LOG(("API Error: " + "pinned folder not loaded for folderId %1, folder: %2" + ).arg(id + ).arg(folderId + )); + return false; + }); + if (!done) { + session().api().requestPinnedDialogs(folder); + } + } break; + + case mtpc_updateChannel: { + auto &d = update.c_updateChannel(); + if (const auto channel = session().data().channelLoaded(d.vchannel_id().v)) { + channel->inviter = UserId(0); + if (channel->amIn()) { + if (channel->isMegagroup() + && !channel->amCreator() + && !channel->hasAdminRights()) { + channel->updateFullForced(); + } + const auto history = channel->owner().history(channel); + //if (const auto feed = channel->feed()) { // #feed + // feed->requestChatListMessage(); + // if (!feed->unreadCountKnown()) { + // feed->owner().histories().requestDialogEntry(feed); + // } + //} else { + history->requestChatListMessage(); + if (!history->unreadCountKnown()) { + history->owner().histories().requestDialogEntry(history); + } + //} + if (!channel->amCreator()) { + session().api().requestSelfParticipant(channel); + } + } + } + } break; + + case mtpc_updateChannelTooLong: { + const auto &d = update.c_updateChannelTooLong(); + if (const auto channel = session().data().channelLoaded(d.vchannel_id().v)) { + const auto pts = d.vpts(); + if (!pts || channel->pts() < pts->v) { + getChannelDifference(channel); + } + } + } break; + + case mtpc_updateChannelMessageViews: { + auto &d = update.c_updateChannelMessageViews(); + if (auto item = session().data().message(d.vchannel_id().v, d.vid().v)) { + item->setViewsCount(d.vviews().v); + } + } break; + + case mtpc_updateChannelAvailableMessages: { + auto &d = update.c_updateChannelAvailableMessages(); + if (const auto channel = session().data().channelLoaded(d.vchannel_id().v)) { + channel->setAvailableMinId(d.vavailable_min_id().v); + if (const auto history = session().data().historyLoaded(channel)) { + history->clearUpTill(d.vavailable_min_id().v); + } + } + } break; + + // Pinned message. + case mtpc_updateUserPinnedMessage: { + const auto &d = update.c_updateUserPinnedMessage(); + if (const auto user = session().data().userLoaded(d.vuser_id().v)) { + user->setPinnedMessageId(d.vid().v); + } + } break; + + case mtpc_updateChatPinnedMessage: { + const auto &d = update.c_updateChatPinnedMessage(); + if (const auto chat = session().data().chatLoaded(d.vchat_id().v)) { + const auto status = chat->applyUpdateVersion(d.vversion().v); + if (status == ChatData::UpdateStatus::Good) { + chat->setPinnedMessageId(d.vid().v); + } + } + } break; + + case mtpc_updateChannelPinnedMessage: { + const auto &d = update.c_updateChannelPinnedMessage(); + if (const auto channel = session().data().channelLoaded(d.vchannel_id().v)) { + channel->setPinnedMessageId(d.vid().v); + } + } break; + + ////// Cloud sticker sets + case mtpc_updateNewStickerSet: { + const auto &d = update.c_updateNewStickerSet(); + session().data().stickers().newSetReceived(d.vstickerset()); + } break; + + case mtpc_updateStickerSetsOrder: { + auto &d = update.c_updateStickerSetsOrder(); + if (!d.is_masks()) { + const auto &order = d.vorder().v; + const auto &sets = session().data().stickers().sets(); + Data::StickersSetsOrder result; + for (const auto &item : order) { + if (sets.find(item.v) == sets.cend()) { + break; + } + result.push_back(item.v); + } + if (result.size() != session().data().stickers().setsOrder().size() + || result.size() != order.size()) { + session().data().stickers().setLastUpdate(0); + session().api().updateStickers(); + } else { + session().data().stickers().setsOrderRef() = std::move(result); + session().local().writeInstalledStickers(); + session().data().stickers().notifyUpdated(); + } + } + } break; + + case mtpc_updateStickerSets: { + session().data().stickers().setLastUpdate(0); + session().api().updateStickers(); + } break; + + case mtpc_updateRecentStickers: { + session().data().stickers().setLastRecentUpdate(0); + session().api().updateStickers(); + } break; + + case mtpc_updateFavedStickers: { + session().data().stickers().setLastFavedUpdate(0); + session().api().updateStickers(); + } break; + + case mtpc_updateReadFeaturedStickers: { + // We read some of the featured stickers, perhaps not all of them. + // Here we don't know what featured sticker sets were read, so we + // request all of them once again. + session().data().stickers().setLastFeaturedUpdate(0); + session().api().updateStickers(); + } break; + + ////// Cloud saved GIFs + case mtpc_updateSavedGifs: { + session().data().stickers().setLastSavedGifsUpdate(0); + session().api().updateStickers(); + } break; + + ////// Cloud drafts + case mtpc_updateDraftMessage: { + const auto &data = update.c_updateDraftMessage(); + const auto peerId = peerFromMTP(data.vpeer()); + data.vdraft().match([&](const MTPDdraftMessage &data) { + Data::ApplyPeerCloudDraft(&session(), peerId, data); + }, [&](const MTPDdraftMessageEmpty &data) { + Data::ClearPeerCloudDraft( + &session(), + peerId, + data.vdate().value_or_empty()); + }); + } break; + + ////// Cloud langpacks + case mtpc_updateLangPack: { + const auto &data = update.c_updateLangPack(); + Lang::CurrentCloudManager().applyLangPackDifference(data.vdifference()); + } break; + + case mtpc_updateLangPackTooLong: { + const auto &data = update.c_updateLangPackTooLong(); + const auto code = qs(data.vlang_code()); + if (!code.isEmpty()) { + Lang::CurrentCloudManager().requestLangPackDifference(code); + } + } break; + + ////// Cloud themes + case mtpc_updateTheme: { + const auto &data = update.c_updateTheme(); + session().data().cloudThemes().applyUpdate(data.vtheme()); + } break; + + } +} + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_updates.h b/Telegram/SourceFiles/api/api_updates.h new file mode 100644 index 000000000..c85b94494 --- /dev/null +++ b/Telegram/SourceFiles/api/api_updates.h @@ -0,0 +1,172 @@ +/* +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 +*/ +#pragma once + +#include "mtproto/mtproto_rpc_sender.h" +#include "data/data_pts_waiter.h" +#include "base/timer.h" + +class ApiWrap; +class History; + +namespace Main { +class Session; +} // namespace Main + +namespace Api { + +class Updates final { +public: + explicit Updates(not_null session); + + [[nodiscard]] Main::Session &session() const; + [[nodiscard]] ApiWrap &api() const; + + void applyUpdates( + const MTPUpdates &updates, + uint64 sentMessageRandomId = 0); + void applyUpdatesNoPtsCheck(const MTPUpdates &updates); + void applyUpdateNoPtsCheck(const MTPUpdate &update); + + void updateOnline(); + [[nodiscard]] bool isIdle() const; + void checkIdleFinish(); + bool lastWasOnline() const; + crl::time lastSetOnline() const; + bool isQuitPrevent(); + + bool updateAndApply(int32 pts, int32 ptsCount, const MTPUpdates &updates); + bool updateAndApply(int32 pts, int32 ptsCount, const MTPUpdate &update); + bool updateAndApply(int32 pts, int32 ptsCount); + + void checkLastUpdate(bool afterSleep); + + // ms <= 0 - stop timer + void ptsWaiterStartTimerFor(ChannelData *channel, crl::time ms); + + void getDifference(); + void requestChannelRangeDifference(not_null history); + + void addActiveChat(rpl::producer chat); + +private: + enum class ChannelDifferenceRequest { + Unknown, + PtsGapOrShortPoll, + AfterFail, + }; + + struct ActiveChatTracker { + PeerData *peer = nullptr; + rpl::lifetime lifetime; + }; + + void channelRangeDifferenceSend( + not_null channel, + MsgRange range, + int32 pts); + void channelRangeDifferenceDone( + not_null channel, + MsgRange range, + const MTPupdates_ChannelDifference &result); + + void updateOnline(bool gotOtherOffline); + void sendPing(); + void getDifferenceByPts(); + void getDifferenceAfterFail(); + + [[nodiscard]] bool requestingDifference() const { + return _ptsWaiter.requesting(); + } + void getChannelDifference( + not_null channel, + ChannelDifferenceRequest from = ChannelDifferenceRequest::Unknown); + void differenceDone(const MTPupdates_Difference &result); + void differenceFail(const RPCError &error); + void feedDifference( + const MTPVector &users, + const MTPVector &chats, + const MTPVector &msgs, + const MTPVector &other); + void stateDone(const MTPupdates_State &state); + void setState(int32 pts, int32 date, int32 qts, int32 seq); + void channelDifferenceDone( + not_null channel, + const MTPupdates_ChannelDifference &diff); + void channelDifferenceFail( + not_null channel, + const RPCError &error); + void failDifferenceStartTimerFor(ChannelData *channel); + void feedChannelDifference(const MTPDupdates_channelDifference &data); + + void mtpUpdateReceived(const MTPUpdates &updates); + void mtpNewSessionCreated(); + void feedUpdateVector( + const MTPVector &updates, + bool skipMessageIds = false); + // Doesn't call sendHistoryChangeNotifications itself. + void feedMessageIds(const MTPVector &updates); + // Doesn't call sendHistoryChangeNotifications itself. + void feedUpdate(const MTPUpdate &update); + + bool whenGetDiffChanged( + ChannelData *channel, + int32 ms, + base::flat_map, crl::time> &whenMap, + crl::time &curTime); + + const not_null _session; + + int32 _updatesDate = 0; + int32 _updatesQts = -1; + int32 _updatesSeq = 0; + base::Timer _noUpdatesTimer; + base::Timer _onlineTimer; + + PtsWaiter _ptsWaiter; + + base::flat_map, crl::time> _whenGetDiffByPts; + base::flat_map, crl::time> _whenGetDiffAfterFail; + crl::time _getDifferenceTimeByPts = 0; + crl::time _getDifferenceTimeAfterFail = 0; + + base::Timer _byPtsTimer; + + base::flat_map _bySeqUpdates; + base::Timer _bySeqTimer; + + base::Timer _byMinChannelTimer; + + // growing timeout for getDifference calls, if it fails + crl::time _failDifferenceTimeout = 1; + // growing timeout for getChannelDifference calls, if it fails + base::flat_map< + not_null, + crl::time> _channelFailDifferenceTimeout; + base::Timer _failDifferenceTimer; + + base::flat_map< + not_null, + mtpRequestId> _rangeDifferenceRequests; + + crl::time _lastUpdateTime = 0; + bool _handlingChannelDifference = false; + + base::flat_map _activeChats; + + mtpRequestId _onlineRequest = 0; + base::Timer _idleFinishTimer; + crl::time _lastSetOnline = 0; + bool _lastWasOnline = false; + bool _isIdle = false; + + rpl::lifetime _lifetime; + +}; + +} // namespace Api diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 19f33dfa5..e98047ca5 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_text_entities.h" #include "api/api_self_destruct.h" #include "api/api_sensitive_content.h" +#include "api/api_updates.h" #include "data/stickers/data_stickers.h" #include "data/data_drafts.h" #include "data/data_photo.h" @@ -272,6 +273,10 @@ Storage::Account &ApiWrap::local() const { return _session->local(); } +Api::Updates &ApiWrap::updates() const { + return _session->updates(); +} + void ApiWrap::setupSupportMode() { if (!_session->supportMode()) { return; @@ -491,12 +496,6 @@ void ApiWrap::importChatInvite(const QString &hash) { }).send(); } -void ApiWrap::applyUpdates( - const MTPUpdates &updates, - uint64 sentMessageRandomId) { - App::main()->feedUpdates(updates, sentMessageRandomId); -} - void ApiWrap::savePinnedOrder(Data::Folder *folder) { const auto &order = _session->data().pinnedChatsOrder( folder, @@ -2321,8 +2320,8 @@ void ApiWrap::deleteConversation(not_null peer, bool revoke) { request(MTPmessages_DeleteChatUser( chat->inputChat, _session->user()->inputUser - )).done([=](const MTPUpdates &updates) { - applyUpdates(updates); + )).done([=](const MTPUpdates &result) { + applyUpdates(result); deleteHistory(peer, false, revoke); }).fail([=](const RPCError &error) { deleteHistory(peer, false, revoke); @@ -2392,6 +2391,12 @@ void ApiWrap::deleteHistory( } } +void ApiWrap::applyUpdates( + const MTPUpdates &updates, + uint64 sentMessageRandomId) { + this->updates().applyUpdates(updates, sentMessageRandomId); +} + int ApiWrap::applyAffectedHistory( not_null peer, const MTPmessages_AffectedHistory &result) { @@ -2399,7 +2404,7 @@ int ApiWrap::applyAffectedHistory( if (const auto channel = peer->asChannel()) { channel->ptsUpdateAndApply(data.vpts().v, data.vpts_count().v); } else { - App::main()->ptsUpdateAndApply(data.vpts().v, data.vpts_count().v); + updates().updateAndApply(data.vpts().v, data.vpts_count().v); } return data.voffset().v; } @@ -2418,7 +2423,26 @@ void ApiWrap::applyAffectedMessages( void ApiWrap::applyAffectedMessages( const MTPmessages_AffectedMessages &result) { const auto &data = result.c_messages_affectedMessages(); - App::main()->ptsUpdateAndApply(data.vpts().v, data.vpts_count().v); + updates().updateAndApply(data.vpts().v, data.vpts_count().v); +} + +void ApiWrap::saveCurrentDraftToCloud() { + Core::App().saveCurrentDraftsToHistories(); + + for (const auto controller : session().windows()) { + if (const auto peer = controller->activeChatCurrent().peer()) { + if (const auto history = session().data().historyLoaded(peer)) { + session().local().writeDrafts(history); + + const auto localDraft = history->localDraft(); + const auto cloudDraft = history->cloudDraft(); + if (!Data::draftsAreEqual(localDraft, cloudDraft) + && !session().supportMode()) { + saveDraftToCloudDelayed(history); + } + } + } + } } void ApiWrap::saveDraftsToCloud() { @@ -2713,98 +2737,6 @@ void ApiWrap::requestParticipantsCountDelayed( [=] { channel->updateFullForced(); }); } -void ApiWrap::requestChannelRangeDifference(not_null history) { - Expects(history->isChannel()); - - const auto channel = history->peer->asChannel(); - if (const auto requestId = _rangeDifferenceRequests.take(channel)) { - request(*requestId).cancel(); - } - const auto range = history->rangeForDifferenceRequest(); - if (!(range.from < range.till) || !channel->pts()) { - return; - } - - MTP_LOG(0, ("getChannelDifference { good - " - "after channelDifferenceTooLong was received, " - "validating history part }%1").arg(cTestMode() ? " TESTMODE" : "")); - channelRangeDifferenceSend(channel, range, channel->pts()); -} - -void ApiWrap::channelRangeDifferenceSend( - not_null channel, - MsgRange range, - int32 pts) { - Expects(range.from < range.till); - - const auto limit = range.till - range.from; - const auto filter = MTP_channelMessagesFilter( - MTP_flags(0), - MTP_vector(1, MTP_messageRange( - MTP_int(range.from), - MTP_int(range.till - 1)))); - const auto requestId = request(MTPupdates_GetChannelDifference( - MTP_flags(MTPupdates_GetChannelDifference::Flag::f_force), - channel->inputChannel, - filter, - MTP_int(pts), - MTP_int(limit) - )).done([=](const MTPupdates_ChannelDifference &result) { - _rangeDifferenceRequests.remove(channel); - channelRangeDifferenceDone(channel, range, result); - }).fail([=](const RPCError &error) { - _rangeDifferenceRequests.remove(channel); - }).send(); - _rangeDifferenceRequests.emplace(channel, requestId); -} - -void ApiWrap::channelRangeDifferenceDone( - not_null channel, - MsgRange range, - const MTPupdates_ChannelDifference &result) { - auto nextRequestPts = int32(0); - auto isFinal = true; - - switch (result.type()) { - case mtpc_updates_channelDifferenceEmpty: { - const auto &d = result.c_updates_channelDifferenceEmpty(); - nextRequestPts = d.vpts().v; - isFinal = d.is_final(); - } break; - - case mtpc_updates_channelDifferenceTooLong: { - const auto &d = result.c_updates_channelDifferenceTooLong(); - - _session->data().processUsers(d.vusers()); - _session->data().processChats(d.vchats()); - - nextRequestPts = d.vdialog().match([&](const MTPDdialog &data) { - return data.vpts().value_or_empty(); - }, [&](const MTPDdialogFolder &data) { - return 0; - }); - isFinal = d.is_final(); - } break; - - case mtpc_updates_channelDifference: { - const auto &d = result.c_updates_channelDifference(); - - App::main()->feedChannelDifference(d); - - nextRequestPts = d.vpts().v; - isFinal = d.is_final(); - } break; - } - - if (!isFinal && nextRequestPts) { - MTP_LOG(0, ("getChannelDifference { " - "good - after not final channelDifference was received, " - "validating history part }%1" - ).arg(cTestMode() ? " TESTMODE" : "")); - channelRangeDifferenceSend(channel, range, nextRequestPts); - } -} - template void ApiWrap::requestFileReference( Data::FileOrigin origin, @@ -3418,224 +3350,6 @@ void ApiWrap::parseRecentChannelParticipants( }, std::move(callbackNotModified)); } -void ApiWrap::applyUpdatesNoPtsCheck(const MTPUpdates &updates) { - switch (updates.type()) { - case mtpc_updateShortMessage: { - const auto &d = updates.c_updateShortMessage(); - const auto flags = mtpCastFlags(d.vflags().v) - | MTPDmessage::Flag::f_from_id; - const auto peerUserId = d.is_out() - ? d.vuser_id() - : MTP_int(_session->userId()); - const auto fwd = d.vfwd_from(); - _session->data().addNewMessage( - MTP_message( - MTP_flags(flags), - d.vid(), - d.is_out() ? MTP_int(_session->userId()) : d.vuser_id(), - MTP_peerUser(peerUserId), - fwd ? (*fwd) : MTPMessageFwdHeader(), - MTP_int(d.vvia_bot_id().value_or_empty()), - MTP_int(d.vreply_to_msg_id().value_or_empty()), - d.vdate(), - d.vmessage(), - MTP_messageMediaEmpty(), - MTPReplyMarkup(), - MTP_vector(d.ventities().value_or_empty()), - MTPint(), - MTPint(), - MTPstring(), - MTPlong(), - //MTPMessageReactions(), - MTPVector()), - MTPDmessage_ClientFlags(), - NewMessageType::Unread); - } break; - - case mtpc_updateShortChatMessage: { - const auto &d = updates.c_updateShortChatMessage(); - const auto flags = mtpCastFlags(d.vflags().v) | MTPDmessage::Flag::f_from_id; - const auto fwd = d.vfwd_from(); - _session->data().addNewMessage( - MTP_message( - MTP_flags(flags), - d.vid(), - d.vfrom_id(), - MTP_peerChat(d.vchat_id()), - fwd ? (*fwd) : MTPMessageFwdHeader(), - MTP_int(d.vvia_bot_id().value_or_empty()), - MTP_int(d.vreply_to_msg_id().value_or_empty()), - d.vdate(), - d.vmessage(), - MTP_messageMediaEmpty(), - MTPReplyMarkup(), - MTP_vector(d.ventities().value_or_empty()), - MTPint(), - MTPint(), - MTPstring(), - MTPlong(), - //MTPMessageReactions(), - MTPVector()), - MTPDmessage_ClientFlags(), - NewMessageType::Unread); - } break; - - case mtpc_updateShortSentMessage: { - auto &d = updates.c_updateShortSentMessage(); - Q_UNUSED(d); // Sent message data was applied anyway. - } break; - - default: Unexpected("Type in applyUpdatesNoPtsCheck()"); - } -} - -void ApiWrap::applyUpdateNoPtsCheck(const MTPUpdate &update) { - switch (update.type()) { - case mtpc_updateNewMessage: { - auto &d = update.c_updateNewMessage(); - auto needToAdd = true; - if (d.vmessage().type() == mtpc_message) { // index forwarded messages to links _overview - const auto &data = d.vmessage().c_message(); - if (_session->data().checkEntitiesAndViewsUpdate(data)) { // already in blocks - LOG(("Skipping message, because it is already in blocks!")); - needToAdd = false; - } - if (needToAdd && !data.is_from_scheduled()) { - // If we still need to add a new message, - // we should first check if this message is in - // the list of scheduled messages. - // This is necessary to correctly update the file reference. - // Note that when a message is scheduled until online - // while the recipient is already online, the server sends - // an ordinary new message with skipped "from_scheduled" flag. - _session->data().scheduledMessages().checkEntitiesAndUpdate( - data); - } - } - if (needToAdd) { - _session->data().addNewMessage( - d.vmessage(), - MTPDmessage_ClientFlags(), - NewMessageType::Unread); - } - } break; - - case mtpc_updateReadMessagesContents: { - const auto &d = update.c_updateReadMessagesContents(); - auto possiblyReadMentions = base::flat_set(); - for (const auto &msgId : d.vmessages().v) { - if (const auto item = _session->data().message(NoChannel, msgId.v)) { - if (item->isUnreadMedia() || item->isUnreadMention()) { - item->markMediaRead(); - _session->data().requestItemRepaint(item); - - if (item->out() - && item->history()->peer->isUser() - && !App::main()->requestingDifference()) { - item->history()->peer->asUser()->madeAction(base::unixtime::now()); - } - } - } else { - // Perhaps it was an unread mention! - possiblyReadMentions.insert(msgId.v); - } - } - checkForUnreadMentions(possiblyReadMentions); - } break; - - case mtpc_updateReadHistoryInbox: { - const auto &d = update.c_updateReadHistoryInbox(); - const auto peer = peerFromMTP(d.vpeer()); - if (const auto history = _session->data().historyLoaded(peer)) { - const auto folderId = d.vfolder_id().value_or_empty(); - history->applyInboxReadUpdate( - folderId, - d.vmax_id().v, - d.vstill_unread_count().v); - } - } break; - - case mtpc_updateReadHistoryOutbox: { - const auto &d = update.c_updateReadHistoryOutbox(); - const auto peer = peerFromMTP(d.vpeer()); - if (const auto history = _session->data().historyLoaded(peer)) { - history->outboxRead(d.vmax_id().v); - if (!App::main()->requestingDifference()) { - if (const auto user = history->peer->asUser()) { - user->madeAction(base::unixtime::now()); - } - } - } - } break; - - case mtpc_updateWebPage: { - auto &d = update.c_updateWebPage(); - Q_UNUSED(d); // Web page was updated anyway. - } break; - - case mtpc_updateFolderPeers: { - const auto &data = update.c_updateFolderPeers(); - auto &owner = _session->data(); - for (const auto &peer : data.vfolder_peers().v) { - peer.match([&](const MTPDfolderPeer &data) { - const auto peerId = peerFromMTP(data.vpeer()); - if (const auto history = owner.historyLoaded(peerId)) { - if (const auto folderId = data.vfolder_id().v) { - history->setFolder(owner.folder(folderId)); - } else { - history->clearFolder(); - } - } - }); - } - } break; - - case mtpc_updateDeleteMessages: { - auto &d = update.c_updateDeleteMessages(); - _session->data().processMessagesDeleted(NoChannel, d.vmessages().v); - } break; - - case mtpc_updateNewChannelMessage: { - auto &d = update.c_updateNewChannelMessage(); - auto needToAdd = true; - if (d.vmessage().type() == mtpc_message) { // index forwarded messages to links _overview - if (_session->data().checkEntitiesAndViewsUpdate(d.vmessage().c_message())) { // already in blocks - LOG(("Skipping message, because it is already in blocks!")); - needToAdd = false; - } - } - if (needToAdd) { - _session->data().addNewMessage( - d.vmessage(), - MTPDmessage_ClientFlags(), - NewMessageType::Unread); - } - } break; - - case mtpc_updateEditChannelMessage: { - auto &d = update.c_updateEditChannelMessage(); - _session->data().updateEditedMessage(d.vmessage()); - } break; - - case mtpc_updateEditMessage: { - auto &d = update.c_updateEditMessage(); - _session->data().updateEditedMessage(d.vmessage()); - } break; - - case mtpc_updateChannelWebPage: { - auto &d = update.c_updateChannelWebPage(); - Q_UNUSED(d); // Web page was updated anyway. - } break; - - case mtpc_updateDeleteChannelMessages: { - auto &d = update.c_updateDeleteChannelMessages(); - _session->data().processMessagesDeleted(d.vchannel_id().v, d.vmessages().v); - } break; - - default: Unexpected("Type in applyUpdateNoPtsCheck()"); - } -} - void ApiWrap::jumpToDate(Dialogs::Key chat, const QDate &date) { if (const auto peer = chat.peer()) { jumpToHistoryDate(peer, date); @@ -4346,9 +4060,8 @@ void ApiWrap::forwardMessages( MTP_vector(randomIds), peer->input, MTP_int(action.options.scheduled) - )).done([=]( - const MTPUpdates &updates) { - applyUpdates(updates); + )).done([=](const MTPUpdates &result) { + applyUpdates(result); if (shared && !--shared->requestsLeft) { shared->callback(); } diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 3d2a2467c..1169fc198 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -66,6 +66,8 @@ inline QString ToString(uint64 value) { } // namespace details +class Updates; + template < typename ...Types, typename = std::enable_if_t<(sizeof...(Types) > 0)>> @@ -139,6 +141,7 @@ public: [[nodiscard]] Main::Session &session() const; [[nodiscard]] Storage::Account &local() const; + [[nodiscard]] Api::Updates &updates() const; void applyUpdates( const MTPUpdates &updates, @@ -154,6 +157,8 @@ public: MTPInputNotifyPeer peer, const MTPPeerNotifySettings &settings); + void saveCurrentDraftToCloud(); + void savePinnedOrder(Data::Folder *folder); void toggleHistoryArchived( not_null history, @@ -194,7 +199,6 @@ public: void requestBots(not_null channel); void requestAdmins(not_null channel); void requestParticipantsCountDelayed(not_null channel); - void requestChannelRangeDifference(not_null history); using UpdatedFileReferences = Data::UpdatedFileReferences; using FileReferencesHandler = FnMut; @@ -310,9 +314,6 @@ public: bool isQuitPrevent(); - void applyUpdatesNoPtsCheck(const MTPUpdates &updates); - void applyUpdateNoPtsCheck(const MTPUpdate &update); - void jumpToDate(Dialogs::Key chat, const QDate &date); void preloadEnoughUnreadMentions(not_null history); @@ -554,15 +555,6 @@ private: mtpRequestId req); void gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result); - void channelRangeDifferenceSend( - not_null channel, - MsgRange range, - int32 pts); - void channelRangeDifferenceDone( - not_null channel, - MsgRange range, - const MTPupdates_ChannelDifference &result); - void stickerSetDisenabled(mtpRequestId requestId); void stickersSaveOrder(); @@ -709,10 +701,6 @@ private: base::flat_set> _selfParticipantRequests; - base::flat_map< - not_null, - mtpRequestId> _rangeDifferenceRequests; - QMap _webPagesPending; base::Timer _webPagesTimer; diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index 2174869ac..8d1864451 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "main/main_session.h" #include "apiwrap.h" +#include "api/api_updates.h" #include "calls/calls_instance.h" #include "lang/lang_file_parser.h" #include "lang/lang_translator.h" @@ -464,13 +465,14 @@ void Application::forceLogOut(const TextWithEntities &explanation) { } void Application::checkLocalTime() { - if (crl::adjust_time()) { + const auto adjusted = crl::adjust_time(); + if (adjusted) { base::Timer::Adjust(); base::ConcurrentTimerEnvironment::Adjust(); base::unixtime::http_invalidate(); - if (App::main()) App::main()->checkLastUpdate(true); - } else { - if (App::main()) App::main()->checkLastUpdate(false); + } + if (activeAccount().sessionExists()) { + activeAccount().session().updates().checkLastUpdate(adjusted); } } @@ -697,6 +699,9 @@ bool Application::passcodeLocked() const { void Application::updateNonIdle() { _lastNonIdleTime = crl::now(); + if (activeAccount().sessionExists()) { + activeAccount().session().updates().checkIdleFinish(); + } } crl::time Application::lastNonIdleTime() const { @@ -794,6 +799,25 @@ rpl::producer Application::lockValue() const { _1 || _2); } +bool Application::hasActiveWindow(not_null session) const { + if (App::quitting() || !_window) { + return false; + } else if (const auto controller = _window->sessionController()) { + if (&controller->session() == session) { + return _window->widget()->isActive(); + } + } + return false; +} + +void Application::saveCurrentDraftsToHistories() { + if (!_window) { + return; + } else if (const auto controller = _window->sessionController()) { + controller->content()->saveFieldToHistoryLocalDraft(); + } +} + Window::Controller *Application::activeWindow() const { return _window.get(); } @@ -897,10 +921,8 @@ void Application::QuitAttempt() { if (IsAppLaunched() && App().activeAccount().sessionExists() && !Sandbox::Instance().isSavingSession()) { - if (const auto mainwidget = App::main()) { - if (mainwidget->isQuitPrevent()) { - prevents = true; - } + if (App().activeAccount().session().updates().isQuitPrevent()) { + prevents = true; } if (App().activeAccount().session().api().isQuitPrevent()) { prevents = true; diff --git a/Telegram/SourceFiles/core/application.h b/Telegram/SourceFiles/core/application.h index 705e6c4f8..edf6d8739 100644 --- a/Telegram/SourceFiles/core/application.h +++ b/Telegram/SourceFiles/core/application.h @@ -37,6 +37,7 @@ void quit(); namespace Main { class Account; +class Session; } // namespace Main namespace Ui { @@ -96,6 +97,8 @@ public: } // Windows interface. + bool hasActiveWindow(not_null session) const; + void saveCurrentDraftsToHistories(); [[nodiscard]] Window::Controller *activeWindow() const; bool closeActiveWindow(); bool minimizeActiveWindow(); diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 9a0fedc4b..1d3746f7a 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -46,7 +46,7 @@ void MegagroupInfo::setLocation(const ChannelLocation &location) { ChannelData::ChannelData(not_null owner, PeerId id) : PeerData(owner, id) , inputChannel(MTP_inputChannel(MTP_int(bareId()), MTP_long(0))) -, _ptsWaiter(&owner->session()) { +, _ptsWaiter(&owner->session().updates()) { Data::PeerFlagValue( this, MTPDchannel::Flag::f_megagroup diff --git a/Telegram/SourceFiles/data/data_pts_waiter.cpp b/Telegram/SourceFiles/data/data_pts_waiter.cpp index f385146fc..316ef0a41 100644 --- a/Telegram/SourceFiles/data/data_pts_waiter.cpp +++ b/Telegram/SourceFiles/data/data_pts_waiter.cpp @@ -7,12 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_pts_waiter.h" -#include "mainwidget.h" -#include "main/main_session.h" -#include "apiwrap.h" -#include "app.h" +#include "api/api_updates.h" -PtsWaiter::PtsWaiter(not_null session) : _session(session) { +PtsWaiter::PtsWaiter(not_null owner) : _owner(owner) { } uint64 PtsWaiter::ptsKey(PtsSkippedQueue queue, int32 pts) { @@ -22,11 +19,9 @@ uint64 PtsWaiter::ptsKey(PtsSkippedQueue queue, int32 pts) { ).first->first; } -void PtsWaiter::setWaitingForSkipped(ChannelData *channel, int32 ms) { +void PtsWaiter::setWaitingForSkipped(ChannelData *channel, crl::time ms) { if (ms >= 0) { - if (App::main()) { - App::main()->ptsWaiterStartTimerFor(channel, ms); - } + _owner->ptsWaiterStartTimerFor(channel, ms); _waitingForSkipped = true; } else { _waitingForSkipped = false; @@ -34,11 +29,9 @@ void PtsWaiter::setWaitingForSkipped(ChannelData *channel, int32 ms) { } } -void PtsWaiter::setWaitingForShortPoll(ChannelData *channel, int32 ms) { +void PtsWaiter::setWaitingForShortPoll(ChannelData *channel, crl::time ms) { if (ms >= 0) { - if (App::main()) { - App::main()->ptsWaiterStartTimerFor(channel, ms); - } + _owner->ptsWaiterStartTimerFor(channel, ms); _waitingForShortPoll = true; } else { _waitingForShortPoll = false; @@ -47,8 +40,8 @@ void PtsWaiter::setWaitingForShortPoll(ChannelData *channel, int32 ms) { } void PtsWaiter::checkForWaiting(ChannelData *channel) { - if (!_waitingForSkipped && !_waitingForShortPoll && App::main()) { - App::main()->ptsWaiterStartTimerFor(channel, -1); + if (!_waitingForSkipped && !_waitingForShortPoll) { + _owner->ptsWaiterStartTimerFor(channel, -1); } } @@ -67,10 +60,10 @@ void PtsWaiter::applySkippedUpdates(ChannelData *channel) { for (auto i = _queue.cbegin(), e = _queue.cend(); i != e; ++i) { switch (i->second) { case SkippedUpdate: { - _session->api().applyUpdateNoPtsCheck(_updateQueue[i->first]); + _owner->applyUpdateNoPtsCheck(_updateQueue[i->first]); } break; case SkippedUpdates: { - _session->api().applyUpdatesNoPtsCheck(_updatesQueue[i->first]); + _owner->applyUpdatesNoPtsCheck(_updatesQueue[i->first]); } break; } } @@ -136,7 +129,7 @@ bool PtsWaiter::updateAndApply( } if (!_waitingForSkipped || _queue.empty()) { // Optimization - no need to put in queue and back. - _session->api().applyUpdatesNoPtsCheck(updates); + _owner->applyUpdatesNoPtsCheck(updates); } else { _updatesQueue.emplace(ptsKey(SkippedUpdates, pts), updates); applySkippedUpdates(channel); @@ -154,7 +147,7 @@ bool PtsWaiter::updateAndApply( } if (!_waitingForSkipped || _queue.empty()) { // Optimization - no need to put in queue and back. - _session->api().applyUpdateNoPtsCheck(update); + _owner->applyUpdateNoPtsCheck(update); } else { _updateQueue.emplace(ptsKey(SkippedUpdate, pts), update); applySkippedUpdates(channel); diff --git a/Telegram/SourceFiles/data/data_pts_waiter.h b/Telegram/SourceFiles/data/data_pts_waiter.h index 1d0568ec4..e35309aa7 100644 --- a/Telegram/SourceFiles/data/data_pts_waiter.h +++ b/Telegram/SourceFiles/data/data_pts_waiter.h @@ -7,9 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -namespace Main { -class Session; -} // namespace Main +namespace Api { +class Updates; +} // namespace Api enum PtsSkippedQueue { SkippedUpdate, @@ -18,7 +18,7 @@ enum PtsSkippedQueue { class PtsWaiter { public: - explicit PtsWaiter(not_null session); + explicit PtsWaiter(not_null owner); // 1s wait for skipped seq or pts in updates. static constexpr auto kWaitForSkippedTimeout = 1000; @@ -45,8 +45,8 @@ public: bool waitingForShortPoll() const { return _waitingForShortPoll; } - void setWaitingForSkipped(ChannelData *channel, int32 ms); // < 0 - not waiting - void setWaitingForShortPoll(ChannelData *channel, int32 ms); // < 0 - not waiting + void setWaitingForSkipped(ChannelData *channel, crl::time ms); // < 0 - not waiting + void setWaitingForShortPoll(ChannelData *channel, crl::time ms); // < 0 - not waiting int32 current() const{ return _good; } @@ -88,7 +88,7 @@ private: uint64 ptsKey(PtsSkippedQueue queue, int32 pts); void checkForWaiting(ChannelData *channel); - const not_null _session; + const not_null _owner; base::flat_map _queue; base::flat_map _updateQueue; base::flat_map _updatesQueue; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 2b016491e..b4aaaade7 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -3272,6 +3272,7 @@ void Session::notifyPollUpdateDelayed(not_null poll) { void Session::sendWebPageGamePollNotifications() { for (const auto page : base::take(_webpagesUpdated)) { + _webpageUpdates.fire_copy(page); const auto i = _webpageViews.find(page); if (i != _webpageViews.end()) { for (const auto view : i->second) { @@ -3295,6 +3296,26 @@ void Session::sendWebPageGamePollNotifications() { } } +rpl::producer> Session::webPageUpdates() const { + return _webpageUpdates.events(); +} + +void Session::channelDifferenceTooLong(not_null channel) { + _channelDifferenceTooLong.fire_copy(channel); +} + +rpl::producer> Session::channelDifferenceTooLong() const { + return _channelDifferenceTooLong.events(); +} + +void Session::historyOutboxRead(not_null history) { + _historyOutboxReads.fire_copy(history); +} + +rpl::producer> Session::historyOutboxReads() const { + return _historyOutboxReads.events(); +} + void Session::registerItemView(not_null view) { _views[view->data()].push_back(view); } diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 508ffd1b8..16fa66e42 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -549,8 +549,15 @@ public: void notifyWebPageUpdateDelayed(not_null page); void notifyGameUpdateDelayed(not_null game); void notifyPollUpdateDelayed(not_null poll); - bool hasPendingWebPageGamePollNotification() const; + [[nodiscard]] bool hasPendingWebPageGamePollNotification() const; void sendWebPageGamePollNotifications(); + [[nodiscard]] rpl::producer> webPageUpdates() const; + + void channelDifferenceTooLong(not_null channel); + [[nodiscard]] rpl::producer> channelDifferenceTooLong() const; + + void historyOutboxRead(not_null history); + [[nodiscard]] rpl::producer> historyOutboxReads() const; void registerItemView(not_null view); void unregisterItemView(not_null view); @@ -868,6 +875,10 @@ private: base::flat_set> _gamesUpdated; base::flat_set> _pollsUpdated; + rpl::event_stream> _webpageUpdates; + rpl::event_stream> _channelDifferenceTooLong; + rpl::event_stream> _historyOutboxReads; + base::flat_multi_map> _pollsClosings; base::Timer _pollsClosingTimer; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 7ef29a5de..6a3f387d0 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -320,6 +320,24 @@ Widget::Widget( }, lifetime()); } +void Widget::setGeometryWithTopMoved( + const QRect &newGeometry, + int topDelta) { + _topDelta = topDelta; + bool willBeResized = (size() != newGeometry.size()); + if (geometry() != newGeometry) { + auto weak = Ui::MakeWeak(this); + setGeometry(newGeometry); + if (!weak) { + return; + } + } + if (!willBeResized) { + resizeEvent(nullptr); + } + _topDelta = 0; +} + void Widget::setupScrollUpButton() { _scrollToTop->setClickedCallback([=] { if (_scrollToAnimation.animating()) { @@ -707,8 +725,10 @@ void Widget::escape() { void Widget::refreshLoadMoreButton(bool mayBlock, bool isBlocked) { if (!mayBlock) { - _loadMoreChats.destroy(); - updateControlsGeometry(); + if (_loadMoreChats) { + _loadMoreChats.destroy(); + updateControlsGeometry(); + } return; } if (!_loadMoreChats) { @@ -1560,8 +1580,7 @@ void Widget::updateControlsGeometry() { right -= _chooseFromUser->width(); _chooseFromUser->moveToLeft(right, _filter->y()); auto scrollTop = filterAreaTop + filterAreaHeight; - auto addToScroll = controller()->content()->contentScrollAddToY(); - auto newScrollTop = _scroll->scrollTop() + addToScroll; + auto newScrollTop = _scroll->scrollTop() + _topDelta; auto scrollHeight = height() - scrollTop; const auto putBottomButton = [&](object_ptr &button) { if (button && !button->isHidden()) { @@ -1582,7 +1601,7 @@ void Widget::updateControlsGeometry() { if (scrollHeight != wasScrollHeight) { controller()->floatPlayerAreaUpdated().notify(true); } - if (addToScroll) { + if (_topDelta) { _scroll->scrollToY(newScrollTop); } else { onListScroll(); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 8357bb6b4..918315b5e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -55,6 +55,11 @@ class Widget : public Window::AbstractSectionWidget, public RPCSender { public: Widget(QWidget *parent, not_null controller); + // When resizing the widget with top edge moved up or down and we + // want to add this top movement to the scroll position, so inner + // content will not move. + void setGeometryWithTopMoved(const QRect &newGeometry, int topDelta); + void updateDragInScroll(bool inScroll); void searchInChat(Key chat); @@ -233,6 +238,8 @@ private: object_ptr _draggingScrollTimer = { nullptr }; int _draggingScrollDelta = 0; + int _topDelta = 0; + }; } // namespace Dialogs diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 5fba0d408..1bef57046 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/localstorage.h" #include "storage/storage_facade.h" #include "storage/storage_shared_media.h" +#include "storage/storage_account.h" //#include "storage/storage_feed_messages.h" // #feed #include "support/support_helper.h" #include "ui/image/image.h" @@ -336,7 +337,7 @@ void History::clearEditDraft() { void History::draftSavedToCloud() { updateChatListEntry(); - if (App::main()) App::main()->writeDrafts(this); + session().local().writeDrafts(this); } HistoryItemsList History::validateForwardDraft() { @@ -1760,6 +1761,7 @@ void History::outboxRead(MsgId upTo) { } } updateChatListEntry(); + session().data().historyOutboxRead(this); } void History::outboxRead(not_null wasRead) { diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 58da4cf4a..d085af606 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -198,6 +198,12 @@ HistoryInner::HistoryInner( }) | rpl::start_with_next([this](not_null view) { mouseActionUpdate(); }, lifetime()); + session().data().historyOutboxReads( + ) | rpl::filter([=](not_null history) { + return (_history == history); + }) | rpl::start_with_next([=] { + update(); + }, lifetime()); } Main::Session &HistoryInner::session() const { diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index cf700636c..05e964f10 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -512,6 +512,21 @@ HistoryWidget::HistoryWidget( } }, lifetime()); + session().data().webPageUpdates( + ) | rpl::filter([=](not_null page) { + return (_previewData == page.get()); + }) | rpl::start_with_next([=] { + updatePreview(); + }, lifetime()); + + session().data().channelDifferenceTooLong( + ) | rpl::filter([=](not_null channel) { + return _peer == channel.get(); + }) | rpl::start_with_next([=] { + updateHistoryDownVisibility(); + preloadHistoryIfNeeded(); + }, lifetime()); + session().data().userIsBotChanges( ) | rpl::filter([=](not_null user) { return (_peer == user.get()); @@ -681,6 +696,24 @@ HistoryWidget::HistoryWidget( setupShortcuts(); } +void HistoryWidget::setGeometryWithTopMoved( + const QRect &newGeometry, + int topDelta) { + _topDelta = topDelta; + bool willBeResized = (size() != newGeometry.size()); + if (geometry() != newGeometry) { + auto weak = Ui::MakeWeak(this); + setGeometry(newGeometry); + if (!weak) { + return; + } + } + if (!willBeResized) { + resizeEvent(nullptr); + } + _topDelta = 0; +} + void HistoryWidget::refreshTabbedPanel() { if (_peer && controller()->hasTabbedSelectorOwnership()) { createTabbedPanel(); @@ -1230,7 +1263,7 @@ void HistoryWidget::saveFieldToHistoryLocalDraft() { } void HistoryWidget::onCloudDraftSave() { - controller()->content()->saveDraftToCloud(); + controller()->session().api().saveCurrentDraftToCloud(); } void HistoryWidget::writeDrafts(Data::Draft **localDraft, Data::Draft **editDraft) { @@ -1735,7 +1768,7 @@ void HistoryWidget::showHistory( // Removing focus from list clears selected and updates top bar. setFocus(); } - controller()->content()->saveDraftToCloud(); + controller()->session().api().saveCurrentDraftToCloud(); if (_migrated) { _migrated->clearLocalDraft(); // use migrated draft only once _migrated->clearEditDraft(); @@ -2550,7 +2583,7 @@ bool HistoryWidget::doWeReadMentions() const { && !_firstLoadRequest && !_delayedShowAtRequest && !_a_show.animating() - && App::wnd()->doWeMarkAsRead(); + && controller()->widget()->doWeMarkAsRead(); } void HistoryWidget::checkHistoryActivation() { @@ -3022,7 +3055,7 @@ void HistoryWidget::saveEditMsgDone(History *history, const MTPUpdates &updates, if (auto editDraft = history->editDraft()) { if (editDraft->saveRequestId == req) { history->clearEditDraft(); - controller()->content()->writeDrafts(history); + session().local().writeDrafts(history); } } } @@ -5028,7 +5061,7 @@ void HistoryWidget::updateControlsGeometry() { } } - updateHistoryGeometry(false, false, { ScrollChangeAdd, controller()->content()->contentScrollAddToY() }); + updateHistoryGeometry(false, false, { ScrollChangeAdd, _topDelta }); updateFieldSize(); diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index c324894cd..95a433937 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -112,6 +112,11 @@ public: void historyLoaded(); + // When resizing the widget with top edge moved up or down and we + // want to add this top movement to the scroll position, so inner + // content will not move. + void setGeometryWithTopMoved(const QRect &newGeometry, int topDelta); + void windowShown(); [[nodiscard]] bool doWeReadServerHistory() const; [[nodiscard]] bool doWeReadMentions() const; @@ -820,4 +825,6 @@ private: object_ptr _topShadow; bool _inGrab = false; + int _topDelta = 0; + }; diff --git a/Telegram/SourceFiles/main/main_account.cpp b/Telegram/SourceFiles/main/main_account.cpp index 460995c67..0e21790ab 100644 --- a/Telegram/SourceFiles/main/main_account.cpp +++ b/Telegram/SourceFiles/main/main_account.cpp @@ -20,6 +20,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/image/image.h" #include "mainwidget.h" #include "observer_peer.h" +#include "api/api_updates.h" +#include "apiwrap.h" #include "main/main_app_config.h" #include "main/main_session.h" #include "facades.h" @@ -353,9 +355,11 @@ void Account::startMtp() { Global::RefConnectionTypeChanged().notify(); } }); - _mtp->setSessionResetHandler([](MTP::ShiftedDcId shiftedDcId) { - if (App::main() && shiftedDcId == MTP::maindc()) { - App::main()->getDifference(); + _mtp->setSessionResetHandler([=](MTP::ShiftedDcId shiftedDcId) { + if (sessionExists()) { + if (shiftedDcId == session().api().instance()->mainDcId()) { + session().updates().getDifference(); + } } }); diff --git a/Telegram/SourceFiles/main/main_session.cpp b/Telegram/SourceFiles/main/main_session.cpp index e2d1f39ca..56a455b26 100644 --- a/Telegram/SourceFiles/main/main_session.cpp +++ b/Telegram/SourceFiles/main/main_session.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "apiwrap.h" +#include "api/api_updates.h" #include "core/application.h" #include "core/changelogs.h" #include "main/main_account.h" @@ -22,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_user.h" #include "window/notifications_manager.h" +#include "window/window_session_controller.h" #include "window/themes/window_theme.h" //#include "platform/platform_specific.h" #include "calls/calls_instance.h" @@ -48,6 +50,7 @@ Session::Session( , _settings(std::move(settings)) , _saveSettingsTimer([=] { local().writeSettings(); }) , _api(std::make_unique(this)) +, _updates(std::make_unique(this)) , _calls(std::make_unique(this)) , _downloader(std::make_unique(_api.get())) , _uploader(std::make_unique(_api.get())) @@ -176,4 +179,20 @@ void Session::saveSettingsNowIfNeeded() { } } +void Session::addWindow(not_null controller) { + _windows.emplace(controller); + controller->lifetime().add([=] { + _windows.remove(controller); + }); + updates().addActiveChat(controller->activeChatChanges( + ) | rpl::map([=](const Dialogs::Key &chat) { + return chat.peer(); + }) | rpl::distinct_until_changed()); +} + +auto Session::windows() const +-> const base::flat_set> & { + return _windows; +} + } // namespace Main diff --git a/Telegram/SourceFiles/main/main_session.h b/Telegram/SourceFiles/main/main_session.h index 6854b8075..1e23100ce 100644 --- a/Telegram/SourceFiles/main/main_session.h +++ b/Telegram/SourceFiles/main/main_session.h @@ -15,6 +15,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class ApiWrap; +namespace Api { +class Updates; +} // namespace Api + namespace MTP { class Instance; } // namespace MTP @@ -39,6 +43,7 @@ namespace Window { namespace Notifications { class System; } // namespace Notifications +class SessionController; } // namespace Window namespace Calls { @@ -81,13 +86,16 @@ public: } bool validateSelf(const MTPUser &user); - [[nodiscard]] Storage::DownloadManagerMtproto &downloader() { + [[nodiscard]] Api::Updates &updates() const { + return *_updates; + } + [[nodiscard]] Storage::DownloadManagerMtproto &downloader() const { return *_downloader; } - [[nodiscard]] Storage::Uploader &uploader() { + [[nodiscard]] Storage::Uploader &uploader() const { return *_uploader; } - [[nodiscard]] Storage::Facade &storage() { + [[nodiscard]] Storage::Facade &storage() const { return *_storage; } [[nodiscard]] Stickers::EmojiPack &emojiStickersPack() const { @@ -112,6 +120,10 @@ public: void saveSettingsDelayed(crl::time delay = kDefaultSaveDelay); void saveSettingsNowIfNeeded(); + void addWindow(not_null controller); + [[nodiscard]] auto windows() const + -> const base::flat_set> &; + [[nodiscard]] not_null mtp(); [[nodiscard]] ApiWrap &api() { return *_api; @@ -143,6 +155,7 @@ private: base::Timer _saveSettingsTimer; const std::unique_ptr _api; + const std::unique_ptr _updates; const std::unique_ptr _calls; const std::unique_ptr _downloader; const std::unique_ptr _uploader; @@ -162,6 +175,8 @@ private: const std::unique_ptr _supportHelper; + base::flat_set> _windows; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 664c3c8ef..88383a676 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include +#include "api/api_updates.h" #include "data/data_photo.h" #include "data/data_document.h" #include "data/data_document_media.h" @@ -112,166 +113,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { -constexpr auto kChannelGetDifferenceLimit = 100; - -// 1s wait after show channel history before sending getChannelDifference. -constexpr auto kWaitForChannelGetDifference = crl::time(1000); - -// If nothing is received in 1 min we ping. -constexpr auto kNoUpdatesTimeout = 60 * 1000; - -// If nothing is received in 1 min when was a sleepmode we ping. -constexpr auto kNoUpdatesAfterSleepTimeout = 60 * crl::time(1000); - // Send channel views each second. constexpr auto kSendViewsTimeout = crl::time(1000); // Cache background scaled image after 3s. constexpr auto kCacheBackgroundTimeout = 3000; -enum class DataIsLoadedResult { - NotLoaded = 0, - FromNotLoaded = 1, - MentionNotLoaded = 2, - Ok = 3, -}; - -bool IsForceLogoutNotification(const MTPDupdateServiceNotification &data) { - return qs(data.vtype()).startsWith(qstr("AUTH_KEY_DROP_")); -} - -bool HasForceLogoutNotification(const MTPUpdates &updates) { - const auto checkUpdate = [](const MTPUpdate &update) { - if (update.type() != mtpc_updateServiceNotification) { - return false; - } - return IsForceLogoutNotification( - update.c_updateServiceNotification()); - }; - const auto checkVector = [&](const MTPVector &list) { - for (const auto &update : list.v) { - if (checkUpdate(update)) { - return true; - } - } - return false; - }; - switch (updates.type()) { - case mtpc_updates: - return checkVector(updates.c_updates().vupdates()); - case mtpc_updatesCombined: - return checkVector(updates.c_updatesCombined().vupdates()); - case mtpc_updateShort: - return checkUpdate(updates.c_updateShort().vupdate()); - } - return false; -} - -bool ForwardedInfoDataLoaded( - not_null session, - const MTPMessageFwdHeader &header) { - return header.match([&](const MTPDmessageFwdHeader &data) { - if (const auto channelId = data.vchannel_id()) { - if (!session->data().channelLoaded(channelId->v)) { - return false; - } - if (const auto fromId = data.vfrom_id()) { - const auto from = session->data().user(fromId->v); - // Minimal loaded is fine in this case. - if (from->loadedStatus == PeerData::NotLoaded) { - return false; - } - } - } else if (const auto fromId = data.vfrom_id()) { - // Fully loaded is required in this case. - if (!session->data().userLoaded(fromId->v)) { - return false; - } - } - return true; - }); -} - -bool MentionUsersLoaded( - not_null session, - const MTPVector &entities) { - for (const auto &entity : entities.v) { - auto type = entity.type(); - if (type == mtpc_messageEntityMentionName) { - if (!session->data().userLoaded(entity.c_messageEntityMentionName().vuser_id().v)) { - return false; - } - } else if (type == mtpc_inputMessageEntityMentionName) { - auto &inputUser = entity.c_inputMessageEntityMentionName().vuser_id(); - if (inputUser.type() == mtpc_inputUser) { - if (!session->data().userLoaded(inputUser.c_inputUser().vuser_id().v)) { - return false; - } - } - } - } - return true; -} - -DataIsLoadedResult AllDataLoadedForMessage( - not_null session, - const MTPMessage &message) { - return message.match([&](const MTPDmessage &message) { - if (const auto fromId = message.vfrom_id()) { - if (!message.is_post() - && !session->data().userLoaded(fromId->v)) { - return DataIsLoadedResult::FromNotLoaded; - } - } - if (const auto viaBotId = message.vvia_bot_id()) { - if (!session->data().userLoaded(viaBotId->v)) { - return DataIsLoadedResult::NotLoaded; - } - } - if (const auto fwd = message.vfwd_from()) { - if (!ForwardedInfoDataLoaded(session, *fwd)) { - return DataIsLoadedResult::NotLoaded; - } - } - if (const auto entities = message.ventities()) { - if (!MentionUsersLoaded(session, *entities)) { - return DataIsLoadedResult::MentionNotLoaded; - } - } - return DataIsLoadedResult::Ok; - }, [&](const MTPDmessageService &message) { - if (const auto fromId = message.vfrom_id()) { - if (!message.is_post() - && !session->data().userLoaded(fromId->v)) { - return DataIsLoadedResult::FromNotLoaded; - } - } - return message.vaction().match( - [&](const MTPDmessageActionChatAddUser &action) { - for (const MTPint &userId : action.vusers().v) { - if (!session->data().userLoaded(userId.v)) { - return DataIsLoadedResult::NotLoaded; - } - } - return DataIsLoadedResult::Ok; - }, [&](const MTPDmessageActionChatJoinedByLink &action) { - if (!session->data().userLoaded(action.vinviter_id().v)) { - return DataIsLoadedResult::NotLoaded; - } - return DataIsLoadedResult::Ok; - }, [&](const MTPDmessageActionChatDeleteUser &action) { - if (!session->data().userLoaded(action.vuser_id().v)) { - return DataIsLoadedResult::NotLoaded; - } - return DataIsLoadedResult::Ok; - }, [](const auto &) { - return DataIsLoadedResult::Ok; - }); - }, [](const MTPDmessageEmpty &message) { - return DataIsLoadedResult::Ok; - }); -} - } // namespace enum StackItemType { @@ -385,14 +232,6 @@ MainWidget::MainWidget( , _dialogs(this, _controller) , _history(this, _controller) , _playerPlaylist(this, _controller) -, _noUpdatesTimer([=] { sendPing(); }) -, _ptsWaiter(&controller->session()) -, _byPtsTimer([=] { getDifferenceByPts(); }) -, _bySeqTimer([=] { getDifference(); }) -, _byMinChannelTimer([=] { getDifference(); }) -, _onlineTimer([=] { updateOnline(); }) -, _idleFinishTimer([=] { checkIdleFinish(); }) -, _failDifferenceTimer([=] { getDifferenceAfterFail(); }) , _cacheBackgroundTimer([=] { cacheBackground(); }) , _viewsIncrementTimer([=] { viewsIncrement(); }) { _controller->setDefaultFloatPlayerDelegate(floatPlayerDelegate()); @@ -410,7 +249,6 @@ MainWidget::MainWidget( _selfUserpicView); }, lifetime()); - _ptsWaiter.setRequesting(true); updateScrollColors(); setupConnectingWidget(); @@ -445,16 +283,6 @@ MainWidget::MainWidget( [this] { updateControlsGeometry(); }, lifetime()); - session().account().mtpUpdates( - ) | rpl::start_with_next([=](const MTPUpdates &updates) { - mtpUpdateReceived(updates); - }, lifetime()); - - session().account().mtpNewSessionCreated( - ) | rpl::start_with_next([=] { - mtpNewSessionCreated(); - }, lifetime()); - // MSVC BUG + REGRESSION rpl::mappers::tuple :( using namespace rpl::mappers; _controller->activeChatValue( @@ -521,6 +349,8 @@ MainWidget::MainWidget( } } +MainWidget::~MainWidget() = default; + Main::Session &MainWidget::session() const { return _controller->session(); } @@ -1042,14 +872,6 @@ void MainWidget::itemEdited(not_null item) { } } -void MainWidget::checkLastUpdate(bool afterSleep) { - auto n = crl::now(); - if (_lastUpdateTime && n > _lastUpdateTime + (afterSleep ? kNoUpdatesAfterSleepTimeout : kNoUpdatesTimeout)) { - _lastUpdateTime = n; - MTP::ping(); - } -} - void MainWidget::handleAudioUpdate(const Media::Player::TrackState &state) { using State = Media::Player::State; const auto document = state.id.audio(); @@ -1298,10 +1120,6 @@ void MainWidget::dialogsCancelled() { _history->activate(); } -bool MainWidget::isIdle() const { - return _isIdle; -} - void MainWidget::clearCachedBackground() { _cachedBackground = QPixmap(); _cacheBackgroundTimer.cancel(); @@ -1676,10 +1494,6 @@ void MainWidget::ui_showPeerHistory( } else { const auto nowActivePeer = _controller->activeChatCurrent().peer(); if (nowActivePeer && nowActivePeer != wasActivePeer) { - if (const auto channel = nowActivePeer->asChannel()) { - channel->ptsWaitingForShortPoll( - kWaitForChannelGetDifference); - } _viewsIncremented.remove(nowActivePeer); } if (Adaptive::OneColumn() && !_dialogs->isHidden()) { @@ -2168,10 +1982,6 @@ void MainWidget::windowShown() { _history->windowShown(); } -void MainWidget::sentUpdatesReceived(uint64 randomId, const MTPUpdates &result) { - feedUpdates(result, randomId); -} - void MainWidget::historyToDown(History *history) { _history->historyToDown(history); } @@ -2379,8 +2189,8 @@ void MainWidget::updateControlsGeometry() { mainSectionTop, dialogsWidth, height() - mainSectionTop); - _dialogs->setGeometry(mainSectionGeometry); - _history->setGeometry(mainSectionGeometry); + _dialogs->setGeometryWithTopMoved(mainSectionGeometry, _contentScrollAddToY); + _history->setGeometryWithTopMoved(mainSectionGeometry, _contentScrollAddToY); if (_hider) _hider->setGeometry(0, 0, dialogsWidth, height()); } else { auto thirdSectionWidth = _thirdSection ? _thirdColumnWidth : 0; @@ -2395,7 +2205,7 @@ void MainWidget::updateControlsGeometry() { accumulate_min(dialogsWidth, width() - st::columnMinimalWidthMain); auto mainSectionWidth = width() - dialogsWidth - thirdSectionWidth; - _dialogs->setGeometryToLeft(0, 0, dialogsWidth, height()); + _dialogs->setGeometryWithTopMoved({ 0, 0, dialogsWidth, height() }, _contentScrollAddToY); _sideShadow->setGeometryToLeft(dialogsWidth, 0, st::lineWidth, height()); if (_thirdShadow) { _thirdShadow->setGeometryToLeft( @@ -2418,7 +2228,7 @@ void MainWidget::updateControlsGeometry() { dialogsWidth, _callTopBarHeight + _exportTopBarHeight); } - _history->setGeometryToLeft(dialogsWidth, mainSectionTop, mainSectionWidth, height() - mainSectionTop); + _history->setGeometryWithTopMoved({ dialogsWidth, mainSectionTop, mainSectionWidth, height() - mainSectionTop }, _contentScrollAddToY); if (_hider) { _hider->setGeometryToLeft(dialogsWidth, 0, mainSectionWidth, height()); } @@ -2651,10 +2461,6 @@ void MainWidget::updateMediaPlaylistPosition(int x) { } } -int MainWidget::contentScrollAddToY() const { - return _contentScrollAddToY; -} - void MainWidget::returnTabbedSelector() { if (!_mainSection || !_mainSection->returnTabbedSelector()) { _history->returnTabbedSelector(); @@ -2789,398 +2595,6 @@ void MainWidget::searchInChat(Dialogs::Key chat) { } } -void MainWidget::feedUpdateVector( - const MTPVector &updates, - bool skipMessageIds) { - for (const auto &update : updates.v) { - if (skipMessageIds && update.type() == mtpc_updateMessageID) { - continue; - } - feedUpdate(update); - } - session().data().sendHistoryChangeNotifications(); -} - -void MainWidget::feedMessageIds(const MTPVector &updates) { - for (const auto &update : updates.v) { - if (update.type() == mtpc_updateMessageID) { - feedUpdate(update); - } - } -} - -void MainWidget::updSetState(int32 pts, int32 date, int32 qts, int32 seq) { - if (pts) { - _ptsWaiter.init(pts); - } - if (updDate < date && !_byMinChannelTimer.isActive()) { - updDate = date; - } - if (qts && updQts < qts) { - updQts = qts; - } - if (seq && seq != updSeq) { - updSeq = seq; - if (_bySeqTimer.isActive()) { - _bySeqTimer.cancel(); - } - for (QMap::iterator i = _bySeqUpdates.begin(); i != _bySeqUpdates.end();) { - int32 s = i.key(); - if (s <= seq + 1) { - MTPUpdates v = i.value(); - i = _bySeqUpdates.erase(i); - if (s == seq + 1) { - return feedUpdates(v); - } - } else { - if (!_bySeqTimer.isActive()) { - _bySeqTimer.callOnce(PtsWaiter::kWaitForSkippedTimeout); - } - break; - } - } - } -} - -void MainWidget::gotChannelDifference( - ChannelData *channel, - const MTPupdates_ChannelDifference &difference) { - _channelFailDifferenceTimeout.remove(channel); - - const auto timeout = difference.match([&](const auto &data) { - return data.vtimeout().value_or_empty(); - }); - const auto isFinal = difference.match([&](const auto &data) { - return data.is_final(); - }); - difference.match([&](const MTPDupdates_channelDifferenceEmpty &data) { - channel->ptsInit(data.vpts().v); - }, [&](const MTPDupdates_channelDifferenceTooLong &data) { - session().data().processUsers(data.vusers()); - session().data().processChats(data.vchats()); - const auto history = session().data().historyLoaded(channel->id); - if (history) { - history->setNotLoadedAtBottom(); - session().api().requestChannelRangeDifference(history); - } - data.vdialog().match([&](const MTPDdialog &data) { - if (const auto pts = data.vpts()) { - channel->ptsInit(pts->v); - } - }, [&](const MTPDdialogFolder &) { - }); - session().data().applyDialogs( - nullptr, - data.vmessages().v, - QVector(1, data.vdialog())); - if (_history->peer() == channel) { - _history->updateHistoryDownVisibility(); - _history->preloadHistoryIfNeeded(); - } - }, [&](const MTPDupdates_channelDifference &data) { - feedChannelDifference(data); - channel->ptsInit(data.vpts().v); - }); - - channel->ptsSetRequesting(false); - - if (!isFinal) { - MTP_LOG(0, ("getChannelDifference { good - after not final channelDifference was received }%1").arg(cTestMode() ? " TESTMODE" : "")); - getChannelDifference(channel); - } else if (_controller->activeChatCurrent().peer() == channel) { - channel->ptsWaitingForShortPoll(timeout - ? (timeout * crl::time(1000)) - : kWaitForChannelGetDifference); - } -} - -void MainWidget::feedChannelDifference( - const MTPDupdates_channelDifference &data) { - session().data().processUsers(data.vusers()); - session().data().processChats(data.vchats()); - - _handlingChannelDifference = true; - feedMessageIds(data.vother_updates()); - session().data().processMessages( - data.vnew_messages(), - NewMessageType::Unread); - feedUpdateVector(data.vother_updates(), true); - _handlingChannelDifference = false; -} - -bool MainWidget::failChannelDifference(ChannelData *channel, const RPCError &error) { - if (MTP::isDefaultHandledError(error)) return false; - - LOG(("RPC Error in getChannelDifference: %1 %2: %3").arg(error.code()).arg(error.type()).arg(error.description())); - failDifferenceStartTimerFor(channel); - return true; -} - -void MainWidget::gotState(const MTPupdates_State &state) { - auto &d = state.c_updates_state(); - updSetState(d.vpts().v, d.vdate().v, d.vqts().v, d.vseq().v); - - _lastUpdateTime = crl::now(); - _noUpdatesTimer.callOnce(kNoUpdatesTimeout); - _ptsWaiter.setRequesting(false); - - session().api().requestDialogs(); - updateOnline(); -} - -void MainWidget::gotDifference(const MTPupdates_Difference &difference) { - _failDifferenceTimeout = 1; - - switch (difference.type()) { - case mtpc_updates_differenceEmpty: { - auto &d = difference.c_updates_differenceEmpty(); - updSetState(_ptsWaiter.current(), d.vdate().v, updQts, d.vseq().v); - - _lastUpdateTime = crl::now(); - _noUpdatesTimer.callOnce(kNoUpdatesTimeout); - - _ptsWaiter.setRequesting(false); - } break; - case mtpc_updates_differenceSlice: { - auto &d = difference.c_updates_differenceSlice(); - feedDifference(d.vusers(), d.vchats(), d.vnew_messages(), d.vother_updates()); - - auto &s = d.vintermediate_state().c_updates_state(); - updSetState(s.vpts().v, s.vdate().v, s.vqts().v, s.vseq().v); - - _ptsWaiter.setRequesting(false); - - MTP_LOG(0, ("getDifference { good - after a slice of difference was received }%1").arg(cTestMode() ? " TESTMODE" : "")); - getDifference(); - } break; - case mtpc_updates_difference: { - auto &d = difference.c_updates_difference(); - feedDifference(d.vusers(), d.vchats(), d.vnew_messages(), d.vother_updates()); - - gotState(d.vstate()); - } break; - case mtpc_updates_differenceTooLong: { - auto &d = difference.c_updates_differenceTooLong(); - LOG(("API Error: updates.differenceTooLong is not supported by Telegram Desktop!")); - } break; - }; -} - -bool MainWidget::getDifferenceTimeChanged( - ChannelData *channel, - int32 ms, - ChannelGetDifferenceTime &channelCurTime, - crl::time &curTime) { - if (channel) { - if (ms <= 0) { - ChannelGetDifferenceTime::iterator i = channelCurTime.find(channel); - if (i != channelCurTime.cend()) { - channelCurTime.erase(i); - } else { - return false; - } - } else { - auto when = crl::now() + ms; - ChannelGetDifferenceTime::iterator i = channelCurTime.find(channel); - if (i != channelCurTime.cend()) { - if (i.value() > when) { - i.value() = when; - } else { - return false; - } - } else { - channelCurTime.insert(channel, when); - } - } - } else { - if (ms <= 0) { - if (curTime) { - curTime = 0; - } else { - return false; - } - } else { - auto when = crl::now() + ms; - if (!curTime || curTime > when) { - curTime = when; - } else { - return false; - } - } - } - return true; -} - -void MainWidget::ptsWaiterStartTimerFor(ChannelData *channel, int32 ms) { - if (getDifferenceTimeChanged(channel, ms, _channelGetDifferenceTimeByPts, _getDifferenceTimeByPts)) { - getDifferenceByPts(); - } -} - -void MainWidget::failDifferenceStartTimerFor(ChannelData *channel) { - auto &timeout = [&]() -> int32& { - if (!channel) { - return _failDifferenceTimeout; - } - const auto i = _channelFailDifferenceTimeout.find(channel); - return (i == _channelFailDifferenceTimeout.end()) - ? _channelFailDifferenceTimeout.insert(channel, 1).value() - : i.value(); - }(); - if (getDifferenceTimeChanged(channel, timeout * 1000, _channelGetDifferenceTimeAfterFail, _getDifferenceTimeAfterFail)) { - getDifferenceAfterFail(); - } - if (timeout < 64) timeout *= 2; -} - -bool MainWidget::ptsUpdateAndApply(int32 pts, int32 ptsCount, const MTPUpdates &updates) { - return _ptsWaiter.updateAndApply(nullptr, pts, ptsCount, updates); -} - -bool MainWidget::ptsUpdateAndApply(int32 pts, int32 ptsCount, const MTPUpdate &update) { - return _ptsWaiter.updateAndApply(nullptr, pts, ptsCount, update); -} - -bool MainWidget::ptsUpdateAndApply(int32 pts, int32 ptsCount) { - return _ptsWaiter.updateAndApply(nullptr, pts, ptsCount); -} - -void MainWidget::feedDifference( - const MTPVector &users, - const MTPVector &chats, - const MTPVector &msgs, - const MTPVector &other) { - Core::App().checkAutoLock(); - session().data().processUsers(users); - session().data().processChats(chats); - feedMessageIds(other); - session().data().processMessages(msgs, NewMessageType::Unread); - feedUpdateVector(other, true); -} - -bool MainWidget::failDifference(const RPCError &error) { - if (MTP::isDefaultHandledError(error)) return false; - - LOG(("RPC Error in getDifference: %1 %2: %3").arg(error.code()).arg(error.type()).arg(error.description())); - failDifferenceStartTimerFor(nullptr); - return true; -} - -void MainWidget::getDifferenceByPts() { - auto now = crl::now(), wait = crl::time(0); - if (_getDifferenceTimeByPts) { - if (_getDifferenceTimeByPts > now) { - wait = _getDifferenceTimeByPts - now; - } else { - getDifference(); - } - } - for (ChannelGetDifferenceTime::iterator i = _channelGetDifferenceTimeByPts.begin(); i != _channelGetDifferenceTimeByPts.cend();) { - if (i.value() > now) { - wait = wait ? qMin(wait, i.value() - now) : (i.value() - now); - ++i; - } else { - getChannelDifference(i.key(), ChannelDifferenceRequest::PtsGapOrShortPoll); - i = _channelGetDifferenceTimeByPts.erase(i); - } - } - if (wait) { - _byPtsTimer.callOnce(wait); - } else { - _byPtsTimer.cancel(); - } -} - -void MainWidget::getDifferenceAfterFail() { - auto now = crl::now(), wait = crl::time(0); - if (_getDifferenceTimeAfterFail) { - if (_getDifferenceTimeAfterFail > now) { - wait = _getDifferenceTimeAfterFail - now; - } else { - _ptsWaiter.setRequesting(false); - MTP_LOG(0, ("getDifference { force - after get difference failed }%1").arg(cTestMode() ? " TESTMODE" : "")); - getDifference(); - } - } - for (auto i = _channelGetDifferenceTimeAfterFail.begin(); i != _channelGetDifferenceTimeAfterFail.cend();) { - if (i.value() > now) { - wait = wait ? qMin(wait, i.value() - now) : (i.value() - now); - ++i; - } else { - getChannelDifference(i.key(), ChannelDifferenceRequest::AfterFail); - i = _channelGetDifferenceTimeAfterFail.erase(i); - } - } - if (wait) { - _failDifferenceTimer.callOnce(wait); - } else { - _failDifferenceTimer.cancel(); - } -} - -void MainWidget::getDifference() { - _getDifferenceTimeByPts = 0; - - if (requestingDifference()) { - return; - } - - _bySeqUpdates.clear(); - _bySeqTimer.cancel(); - - _noUpdatesTimer.cancel(); - _getDifferenceTimeAfterFail = 0; - - _ptsWaiter.setRequesting(true); - - MTP::send( - MTPupdates_GetDifference( - MTP_flags(0), - MTP_int(_ptsWaiter.current()), - MTPint(), - MTP_int(updDate), - MTP_int(updQts)), - rpcDone(&MainWidget::gotDifference), - rpcFail(&MainWidget::failDifference)); -} - -void MainWidget::getChannelDifference( - not_null channel, - ChannelDifferenceRequest from) { - if (from != ChannelDifferenceRequest::PtsGapOrShortPoll) { - _channelGetDifferenceTimeByPts.remove(channel); - } - - if (!channel->ptsInited() || channel->ptsRequesting()) return; - - if (from != ChannelDifferenceRequest::AfterFail) { - _channelGetDifferenceTimeAfterFail.remove(channel); - } - - channel->ptsSetRequesting(true); - - auto filter = MTP_channelMessagesFilterEmpty(); - auto flags = MTPupdates_GetChannelDifference::Flag::f_force | 0; - if (from != ChannelDifferenceRequest::PtsGapOrShortPoll) { - if (!channel->ptsWaitingForSkipped()) { - flags = 0; // No force flag when requesting for short poll. - } - } - MTP::send( - MTPupdates_GetChannelDifference( - MTP_flags(flags), - channel->inputChannel, - filter, - MTP_int(channel->pts()), - MTP_int(kChannelGetDifferenceLimit)), - rpcDone(&MainWidget::gotChannelDifference, channel.get()), - rpcFail(&MainWidget::failChannelDifference, channel.get())); -} - -void MainWidget::sendPing() { - MTP::ping(); -} - void MainWidget::start() { auto &api = session().api(); api.requestNotifySettings(MTP_inputNotifyUsers()); @@ -3190,9 +2604,6 @@ void MainWidget::start() { cSetOtherOnline(0); session().user()->loadUserpic(); - MTP::send(MTPupdates_GetState(), rpcDone(&MainWidget::gotState)); - update(); - _started = true; auto &local = session().local(); local.readInstalledStickers(); @@ -3493,1121 +2904,25 @@ void MainWidget::activate() { } bool MainWidget::isActive() const { - return !_isIdle && isVisible() && !_a_show.animating(); + return isVisible() + && !_a_show.animating() + && !session().updates().isIdle(); } bool MainWidget::doWeMarkAsRead() const { return isActive() && !_mainSection; } -bool MainWidget::lastWasOnline() const { - return _lastWasOnline; -} - -crl::time MainWidget::lastSetOnline() const { - return _lastSetOnline; -} - int32 MainWidget::dlgsWidth() const { return _dialogs->width(); } -MainWidget::~MainWidget() { - if (App::main() == this) { - _history->showHistory(0, 0); - } -} - -void MainWidget::updateOnline(bool gotOtherOffline) { - crl::on_main(this, [] { Core::App().checkAutoLock(); }); - - bool isOnline = !App::quitting() && App::wnd()->isActive(); - int updateIn = Global::OnlineUpdatePeriod(); - Assert(updateIn >= 0); - if (isOnline) { - const auto idle = crl::now() - Core::App().lastNonIdleTime(); - if (idle >= Global::OfflineIdleTimeout()) { - isOnline = false; - if (!_isIdle) { - _isIdle = true; - _idleFinishTimer.callOnce(900); - } - } else { - updateIn = qMin(updateIn, int(Global::OfflineIdleTimeout() - idle)); - Assert(updateIn >= 0); - } - } - auto ms = crl::now(); - if (isOnline != _lastWasOnline - || (isOnline && _lastSetOnline + Global::OnlineUpdatePeriod() <= ms) - || (isOnline && gotOtherOffline)) { - if (_onlineRequest) { - MTP::cancel(_onlineRequest); - _onlineRequest = 0; - } - - _lastWasOnline = isOnline; - _lastSetOnline = ms; - if (!App::quitting()) { - _onlineRequest = MTP::send(MTPaccount_UpdateStatus(MTP_bool(!isOnline))); - } else { - _onlineRequest = MTP::send( - MTPaccount_UpdateStatus(MTP_bool(!isOnline)), - rpcDone(&MainWidget::updateStatusDone), - rpcFail(&MainWidget::updateStatusFail)); - } - - const auto self = session().user(); - self->onlineTill = base::unixtime::now() + (isOnline ? (Global::OnlineUpdatePeriod() / 1000) : -1); - Notify::peerUpdatedDelayed( - self, - Notify::PeerUpdate::Flag::UserOnlineChanged); - if (!isOnline) { // Went offline, so we need to save message draft to the cloud. - saveDraftToCloud(); - } - - _lastSetOnline = ms; - } else if (isOnline) { - updateIn = qMin(updateIn, int(_lastSetOnline + Global::OnlineUpdatePeriod() - ms)); - Assert(updateIn >= 0); - } - _onlineTimer.callOnce(updateIn); -} - -void MainWidget::updateStatusDone(const MTPBool &result) { - Core::App().quitPreventFinished(); -} - -bool MainWidget::updateStatusFail(const RPCError &error) { - if (MTP::isDefaultHandledError(error)) { - return false; - } - Core::App().quitPreventFinished(); - return true; -} - -bool MainWidget::isQuitPrevent() { - if (!_lastWasOnline) { - return false; - } - LOG(("MainWidget prevents quit, sending offline status...")); - updateOnline(); - return true; -} - -void MainWidget::saveDraftToCloud() { - _history->saveFieldToHistoryLocalDraft(); - - const auto peer = _history->peer(); - if (const auto history = session().data().historyLoaded(peer)) { - writeDrafts(history); - - const auto localDraft = history->localDraft(); - const auto cloudDraft = history->cloudDraft(); - if (!Data::draftsAreEqual(localDraft, cloudDraft) - && !session().supportMode()) { - session().api().saveDraftToCloudDelayed(history); - } - } -} - void MainWidget::applyCloudDraft(History *history) { _history->applyCloudDraft(history); } -void MainWidget::writeDrafts(History *history) { - Storage::MessageDraft storedLocalDraft, storedEditDraft; - MessageCursor localCursor, editCursor; - if (const auto localDraft = history->localDraft()) { - if (session().supportMode() - || !Data::draftsAreEqual(localDraft, history->cloudDraft())) { - storedLocalDraft = Storage::MessageDraft{ - localDraft->msgId, - localDraft->textWithTags, - localDraft->previewCancelled - }; - localCursor = localDraft->cursor; - } - } - if (const auto editDraft = history->editDraft()) { - storedEditDraft = Storage::MessageDraft{ - editDraft->msgId, - editDraft->textWithTags, - editDraft->previewCancelled - }; - editCursor = editDraft->cursor; - } - session().local().writeDrafts( - history->peer->id, - storedLocalDraft, - storedEditDraft); - session().local().writeDraftCursors(history->peer->id, localCursor, editCursor); -} - -void MainWidget::checkIdleFinish() { - if (crl::now() - Core::App().lastNonIdleTime() - < Global::OfflineIdleTimeout()) { - _idleFinishTimer.cancel(); - _isIdle = false; - updateOnline(); - App::wnd()->checkHistoryActivation(); - } else { - _idleFinishTimer.callOnce(900); - } -} - -void MainWidget::mtpNewSessionCreated() { - Core::App().checkAutoLock(); - updSeq = 0; - MTP_LOG(0, ("getDifference { after new_session_created }%1" - ).arg(cTestMode() ? " TESTMODE" : "")); - getDifference(); -} - -void MainWidget::mtpUpdateReceived(const MTPUpdates &updates) { - Core::App().checkAutoLock(); - _lastUpdateTime = crl::now(); - _noUpdatesTimer.callOnce(kNoUpdatesTimeout); - if (!requestingDifference() - || HasForceLogoutNotification(updates)) { - feedUpdates(updates); - } -} - -void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) { - switch (updates.type()) { - case mtpc_updates: { - auto &d = updates.c_updates(); - if (d.vseq().v) { - if (d.vseq().v <= updSeq) { - return; - } - if (d.vseq().v > updSeq + 1) { - _bySeqUpdates.insert(d.vseq().v, updates); - _bySeqTimer.callOnce(PtsWaiter::kWaitForSkippedTimeout); - return; - } - } - - session().data().processUsers(d.vusers()); - session().data().processChats(d.vchats()); - feedUpdateVector(d.vupdates()); - - updSetState(0, d.vdate().v, updQts, d.vseq().v); - } break; - - case mtpc_updatesCombined: { - auto &d = updates.c_updatesCombined(); - if (d.vseq_start().v) { - if (d.vseq_start().v <= updSeq) { - return; - } - if (d.vseq_start().v > updSeq + 1) { - _bySeqUpdates.insert(d.vseq_start().v, updates); - _bySeqTimer.callOnce(PtsWaiter::kWaitForSkippedTimeout); - return; - } - } - - session().data().processUsers(d.vusers()); - session().data().processChats(d.vchats()); - feedUpdateVector(d.vupdates()); - - updSetState(0, d.vdate().v, updQts, d.vseq().v); - } break; - - case mtpc_updateShort: { - auto &d = updates.c_updateShort(); - feedUpdate(d.vupdate()); - - updSetState(0, d.vdate().v, updQts, updSeq); - } break; - - case mtpc_updateShortMessage: { - auto &d = updates.c_updateShortMessage(); - const auto viaBotId = d.vvia_bot_id(); - const auto entities = d.ventities(); - const auto fwd = d.vfwd_from(); - if (!session().data().userLoaded(d.vuser_id().v) - || (viaBotId && !session().data().userLoaded(viaBotId->v)) - || (entities && !MentionUsersLoaded(&session(), *entities)) - || (fwd && !ForwardedInfoDataLoaded(&session(), *fwd))) { - MTP_LOG(0, ("getDifference { good - getting user for updateShortMessage }%1").arg(cTestMode() ? " TESTMODE" : "")); - return getDifference(); - } - if (ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, updates)) { - // Update date as well. - updSetState(0, d.vdate().v, updQts, updSeq); - } - } break; - - case mtpc_updateShortChatMessage: { - auto &d = updates.c_updateShortChatMessage(); - const auto noFrom = !session().data().userLoaded(d.vfrom_id().v); - const auto chat = session().data().chatLoaded(d.vchat_id().v); - const auto viaBotId = d.vvia_bot_id(); - const auto entities = d.ventities(); - const auto fwd = d.vfwd_from(); - if (!chat - || noFrom - || (viaBotId && !session().data().userLoaded(viaBotId->v)) - || (entities && !MentionUsersLoaded(&session(), *entities)) - || (fwd && !ForwardedInfoDataLoaded(&session(), *fwd))) { - MTP_LOG(0, ("getDifference { good - getting user for updateShortChatMessage }%1").arg(cTestMode() ? " TESTMODE" : "")); - if (chat && noFrom) { - session().api().requestFullPeer(chat); - } - return getDifference(); - } - if (ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, updates)) { - // Update date as well. - updSetState(0, d.vdate().v, updQts, updSeq); - } - } break; - - case mtpc_updateShortSentMessage: { - auto &d = updates.c_updateShortSentMessage(); - if (!IsServerMsgId(d.vid().v)) { - LOG(("API Error: Bad msgId got from server: %1").arg(d.vid().v)); - } else if (randomId) { - auto &owner = session().data(); - const auto sent = owner.messageSentData(randomId); - const auto lookupMessage = [&] { - return sent.peerId - ? owner.message(peerToChannel(sent.peerId), d.vid().v) - : nullptr; - }; - if (const auto id = owner.messageIdByRandomId(randomId)) { - const auto local = owner.message(id); - if (local && local->isScheduled()) { - owner.scheduledMessages().sendNowSimpleMessage(d, local); - } - } - const auto wasAlready = (lookupMessage() != nullptr); - feedUpdate(MTP_updateMessageID(d.vid(), MTP_long(randomId))); // ignore real date - if (const auto item = lookupMessage()) { - const auto list = d.ventities(); - if (list && !MentionUsersLoaded(&session(), *list)) { - session().api().requestMessageData( - item->history()->peer->asChannel(), - item->id, - ApiWrap::RequestMessageDataCallback()); - } - item->updateSentContent({ - sent.text, - Api::EntitiesFromMTP(&session(), list.value_or_empty()) - }, d.vmedia()); - item->contributeToSlowmode(d.vdate().v); - if (!wasAlready) { - item->indexAsNewItem(); - } - } - } - - if (ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, updates)) { - // Update date as well. - updSetState(0, d.vdate().v, updQts, updSeq); - } - } break; - - case mtpc_updatesTooLong: { - MTP_LOG(0, ("getDifference { good - updatesTooLong received }%1").arg(cTestMode() ? " TESTMODE" : "")); - return getDifference(); - } break; - } - session().data().sendHistoryChangeNotifications(); -} - -void MainWidget::feedUpdate(const MTPUpdate &update) { - switch (update.type()) { - - // New messages. - case mtpc_updateNewMessage: { - auto &d = update.c_updateNewMessage(); - - const auto isDataLoaded = AllDataLoadedForMessage(&session(), d.vmessage()); - if (!requestingDifference() && isDataLoaded != DataIsLoadedResult::Ok) { - MTP_LOG(0, ("getDifference { good - " - "after not all data loaded in updateNewMessage }%1" - ).arg(cTestMode() ? " TESTMODE" : "")); - - // This can be if this update was created by grouping - // some short message update into an updates vector. - return getDifference(); - } - - ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update); - } break; - - case mtpc_updateNewChannelMessage: { - auto &d = update.c_updateNewChannelMessage(); - auto channel = session().data().channelLoaded(peerToChannel(PeerFromMessage(d.vmessage()))); - const auto isDataLoaded = AllDataLoadedForMessage(&session(), d.vmessage()); - if (!requestingDifference() && (!channel || isDataLoaded != DataIsLoadedResult::Ok)) { - MTP_LOG(0, ("getDifference { good - " - "after not all data loaded in updateNewChannelMessage }%1" - ).arg(cTestMode() ? " TESTMODE" : "")); - - // Request last active supergroup participants if the 'from' user was not loaded yet. - // This will optimize similar getDifference() calls for almost all next messages. - if (isDataLoaded == DataIsLoadedResult::FromNotLoaded && channel && channel->isMegagroup()) { - if (channel->mgInfo->lastParticipants.size() < Global::ChatSizeMax() && (channel->mgInfo->lastParticipants.empty() || channel->mgInfo->lastParticipants.size() < channel->membersCount())) { - session().api().requestLastParticipants(channel); - } - } - - if (!_byMinChannelTimer.isActive()) { // getDifference after timeout - _byMinChannelTimer.callOnce(PtsWaiter::kWaitForSkippedTimeout); - } - return; - } - if (channel && !_handlingChannelDifference) { - if (channel->ptsRequesting()) { // skip global updates while getting channel difference - return; - } - channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update); - } else { - session().api().applyUpdateNoPtsCheck(update); - } - } break; - - case mtpc_updateMessageID: { - const auto &d = update.c_updateMessageID(); - const auto randomId = d.vrandom_id().v; - if (const auto id = session().data().messageIdByRandomId(randomId)) { - const auto newId = d.vid().v; - if (const auto local = session().data().message(id)) { - if (local->isScheduled()) { - session().data().scheduledMessages().apply(d, local); - } else { - const auto channel = id.channel; - const auto existing = session().data().message( - channel, - newId); - if (existing && !local->mainView()) { - const auto history = local->history(); - local->destroy(); - history->requestChatListMessage(); - } else { - if (existing) { - existing->destroy(); - } - local->setRealId(d.vid().v); - } - } - } - session().data().unregisterMessageRandomId(randomId); - } - session().data().unregisterMessageSentData(randomId); - } break; - - // Message contents being read. - case mtpc_updateReadMessagesContents: { - auto &d = update.c_updateReadMessagesContents(); - ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update); - } break; - - case mtpc_updateChannelReadMessagesContents: { - auto &d = update.c_updateChannelReadMessagesContents(); - auto channel = session().data().channelLoaded(d.vchannel_id().v); - if (!channel) { - if (!_byMinChannelTimer.isActive()) { - // getDifference after timeout. - _byMinChannelTimer.callOnce(PtsWaiter::kWaitForSkippedTimeout); - } - return; - } - auto possiblyReadMentions = base::flat_set(); - for_const (auto &msgId, d.vmessages().v) { - if (auto item = session().data().message(channel, msgId.v)) { - if (item->isUnreadMedia() || item->isUnreadMention()) { - item->markMediaRead(); - session().data().requestItemRepaint(item); - } - } else { - // Perhaps it was an unread mention! - possiblyReadMentions.insert(msgId.v); - } - } - session().api().checkForUnreadMentions(possiblyReadMentions, channel); - } break; - - // Edited messages. - case mtpc_updateEditMessage: { - auto &d = update.c_updateEditMessage(); - ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update); - } break; - - case mtpc_updateEditChannelMessage: { - auto &d = update.c_updateEditChannelMessage(); - auto channel = session().data().channelLoaded(peerToChannel(PeerFromMessage(d.vmessage()))); - - if (channel && !_handlingChannelDifference) { - if (channel->ptsRequesting()) { // skip global updates while getting channel difference - return; - } else { - channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update); - } - } else { - session().api().applyUpdateNoPtsCheck(update); - } - } break; - - // Messages being read. - case mtpc_updateReadHistoryInbox: { - auto &d = update.c_updateReadHistoryInbox(); - ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update); - } break; - - case mtpc_updateReadHistoryOutbox: { - auto &d = update.c_updateReadHistoryOutbox(); - if (ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update)) { - // We could've updated the double checks. - // Better would be for history to be subscribed to outbox read events. - _history->update(); - } - } break; - - case mtpc_updateReadChannelInbox: { - const auto &d = update.c_updateReadChannelInbox(); - const auto peer = peerFromChannel(d.vchannel_id().v); - if (const auto history = session().data().historyLoaded(peer)) { - history->applyInboxReadUpdate( - d.vfolder_id().value_or_empty(), - d.vmax_id().v, - d.vstill_unread_count().v, - d.vpts().v); - } - } break; - - case mtpc_updateReadChannelOutbox: { - const auto &d = update.c_updateReadChannelOutbox(); - const auto peer = peerFromChannel(d.vchannel_id().v); - if (const auto history = session().data().historyLoaded(peer)) { - history->outboxRead(d.vmax_id().v); - if (!requestingDifference()) { - if (const auto user = history->peer->asUser()) { - user->madeAction(base::unixtime::now()); - } - } - if (_history->peer() && _history->peer()->id == peer) { - _history->update(); - } - } - } break; - - //case mtpc_updateReadFeed: { // #feed - // const auto &d = update.c_updateReadFeed(); - // const auto feedId = d.vfeed_id().v; - // if (const auto feed = session().data().feedLoaded(feedId)) { - // feed->setUnreadPosition( - // Data::FeedPositionFromMTP(d.vmax_position())); - // if (d.vunread_count() && d.vunread_muted_count()) { - // feed->setUnreadCounts( - // d.vunread_count()->v, - // d.vunread_muted_count()->v); - // } else { - // session().data().histories().requestDialogEntry(feed); - // } - // } - //} break; - - case mtpc_updateDialogUnreadMark: { - const auto &data = update.c_updateDialogUnreadMark(); - data.vpeer().match( - [&](const MTPDdialogPeer &dialog) { - const auto id = peerFromMTP(dialog.vpeer()); - if (const auto history = session().data().historyLoaded(id)) { - history->setUnreadMark(data.is_unread()); - } - }, [&](const MTPDdialogPeerFolder &dialog) { - const auto id = dialog.vfolder_id().v; // #TODO archive - //if (const auto folder = session().data().folderLoaded(id)) { - // folder->setUnreadMark(data.is_unread()); - //} - }); - } break; - - case mtpc_updateFolderPeers: { - const auto &data = update.c_updateFolderPeers(); - - ptsUpdateAndApply(data.vpts().v, data.vpts_count().v, update); - } break; - - case mtpc_updateDialogFilter: - case mtpc_updateDialogFilterOrder: - case mtpc_updateDialogFilters: { - session().data().chatsFilters().apply(update); - } break; - - // Deleted messages. - case mtpc_updateDeleteMessages: { - auto &d = update.c_updateDeleteMessages(); - - ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update); - } break; - - case mtpc_updateDeleteChannelMessages: { - auto &d = update.c_updateDeleteChannelMessages(); - auto channel = session().data().channelLoaded(d.vchannel_id().v); - - if (channel && !_handlingChannelDifference) { - if (channel->ptsRequesting()) { // skip global updates while getting channel difference - return; - } - channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update); - } else { - session().api().applyUpdateNoPtsCheck(update); - } - } break; - - case mtpc_updateNewScheduledMessage: { - const auto &d = update.c_updateNewScheduledMessage(); - session().data().scheduledMessages().apply(d); - } break; - - case mtpc_updateDeleteScheduledMessages: { - const auto &d = update.c_updateDeleteScheduledMessages(); - session().data().scheduledMessages().apply(d); - } break; - - case mtpc_updateWebPage: { - auto &d = update.c_updateWebPage(); - - // Update web page anyway. - session().data().processWebpage(d.vwebpage()); - _history->updatePreview(); - session().data().sendWebPageGamePollNotifications(); - - ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update); - } break; - - case mtpc_updateChannelWebPage: { - auto &d = update.c_updateChannelWebPage(); - - // Update web page anyway. - session().data().processWebpage(d.vwebpage()); - _history->updatePreview(); - session().data().sendWebPageGamePollNotifications(); - - auto channel = session().data().channelLoaded(d.vchannel_id().v); - if (channel && !_handlingChannelDifference) { - if (channel->ptsRequesting()) { // skip global updates while getting channel difference - return; - } else { - channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update); - } - } else { - session().api().applyUpdateNoPtsCheck(update); - } - } break; - - case mtpc_updateMessagePoll: { - session().data().applyUpdate(update.c_updateMessagePoll()); - } break; - - case mtpc_updateUserTyping: { - auto &d = update.c_updateUserTyping(); - const auto userId = peerFromUser(d.vuser_id()); - const auto history = session().data().historyLoaded(userId); - const auto user = session().data().userLoaded(d.vuser_id().v); - if (history && user) { - const auto when = requestingDifference() ? 0 : base::unixtime::now(); - session().data().registerSendAction(history, user, d.vaction(), when); - } - } break; - - case mtpc_updateChatUserTyping: { - auto &d = update.c_updateChatUserTyping(); - const auto history = [&]() -> History* { - if (const auto chat = session().data().chatLoaded(d.vchat_id().v)) { - return session().data().historyLoaded(chat->id); - } else if (const auto channel = session().data().channelLoaded(d.vchat_id().v)) { - return session().data().historyLoaded(channel->id); - } - return nullptr; - }(); - const auto user = (d.vuser_id().v == session().userId()) - ? nullptr - : session().data().userLoaded(d.vuser_id().v); - if (history && user) { - const auto when = requestingDifference() ? 0 : base::unixtime::now(); - session().data().registerSendAction(history, user, d.vaction(), when); - } - } break; - - case mtpc_updateChatParticipants: { - session().data().applyUpdate(update.c_updateChatParticipants()); - } break; - - case mtpc_updateChatParticipantAdd: { - session().data().applyUpdate(update.c_updateChatParticipantAdd()); - } break; - - case mtpc_updateChatParticipantDelete: { - session().data().applyUpdate(update.c_updateChatParticipantDelete()); - } break; - - case mtpc_updateChatParticipantAdmin: { - session().data().applyUpdate(update.c_updateChatParticipantAdmin()); - } break; - - case mtpc_updateChatDefaultBannedRights: { - session().data().applyUpdate(update.c_updateChatDefaultBannedRights()); - } break; - - case mtpc_updateUserStatus: { - auto &d = update.c_updateUserStatus(); - if (auto user = session().data().userLoaded(d.vuser_id().v)) { - switch (d.vstatus().type()) { - case mtpc_userStatusEmpty: user->onlineTill = 0; break; - case mtpc_userStatusRecently: - if (user->onlineTill > -10) { // don't modify pseudo-online - user->onlineTill = -2; - } - break; - case mtpc_userStatusLastWeek: user->onlineTill = -3; break; - case mtpc_userStatusLastMonth: user->onlineTill = -4; break; - case mtpc_userStatusOffline: user->onlineTill = d.vstatus().c_userStatusOffline().vwas_online().v; break; - case mtpc_userStatusOnline: user->onlineTill = d.vstatus().c_userStatusOnline().vexpires().v; break; - } - Notify::peerUpdatedDelayed( - user, - Notify::PeerUpdate::Flag::UserOnlineChanged); - } - if (d.vuser_id().v == session().userId()) { - if (d.vstatus().type() == mtpc_userStatusOffline || d.vstatus().type() == mtpc_userStatusEmpty) { - updateOnline(true); - if (d.vstatus().type() == mtpc_userStatusOffline) { - cSetOtherOnline(d.vstatus().c_userStatusOffline().vwas_online().v); - } - } else if (d.vstatus().type() == mtpc_userStatusOnline) { - cSetOtherOnline(d.vstatus().c_userStatusOnline().vexpires().v); - } - } - } break; - - case mtpc_updateUserName: { - auto &d = update.c_updateUserName(); - if (auto user = session().data().userLoaded(d.vuser_id().v)) { - if (!user->isContact()) { - user->setName( - TextUtilities::SingleLine(qs(d.vfirst_name())), - TextUtilities::SingleLine(qs(d.vlast_name())), - user->nameOrPhone, - TextUtilities::SingleLine(qs(d.vusername()))); - } else { - user->setName( - TextUtilities::SingleLine(user->firstName), - TextUtilities::SingleLine(user->lastName), - user->nameOrPhone, - TextUtilities::SingleLine(qs(d.vusername()))); - } - } - } break; - - case mtpc_updateUserPhoto: { - auto &d = update.c_updateUserPhoto(); - if (auto user = session().data().userLoaded(d.vuser_id().v)) { - user->setPhoto(d.vphoto()); - user->loadUserpic(); - if (mtpIsTrue(d.vprevious()) || !user->userpicPhotoId()) { - session().storage().remove(Storage::UserPhotosRemoveAfter( - user->bareId(), - user->userpicPhotoId())); - } else { - session().storage().add(Storage::UserPhotosAddNew( - user->bareId(), - user->userpicPhotoId())); - } - } - } break; - - case mtpc_updatePeerSettings: { - const auto &d = update.c_updatePeerSettings(); - const auto peerId = peerFromMTP(d.vpeer()); - if (const auto peer = session().data().peerLoaded(peerId)) { - const auto settings = d.vsettings().match([]( - const MTPDpeerSettings &data) { - return data.vflags().v; - }); - peer->setSettings(settings); - } - } break; - - case mtpc_updateNotifySettings: { - auto &d = update.c_updateNotifySettings(); - session().data().applyNotifySetting(d.vpeer(), d.vnotify_settings()); - } break; - - case mtpc_updateDcOptions: { - auto &d = update.c_updateDcOptions(); - Core::App().dcOptions()->addFromList(d.vdc_options()); - } break; - - case mtpc_updateConfig: { - session().mtp()->requestConfig(); - } break; - - case mtpc_updateUserPhone: { - const auto &d = update.c_updateUserPhone(); - if (const auto user = session().data().userLoaded(d.vuser_id().v)) { - const auto newPhone = qs(d.vphone()); - if (newPhone != user->phone()) { - user->setPhone(newPhone); - user->setName( - user->firstName, - user->lastName, - ((user->isContact() - || user->isServiceUser() - || user->isSelf() - || user->phone().isEmpty()) - ? QString() - : App::formatPhone(user->phone())), - user->username); - - Notify::peerUpdatedDelayed( - user, - Notify::PeerUpdate::Flag::UserPhoneChanged); - } - } - } break; - - case mtpc_updateNewEncryptedMessage: { - auto &d = update.c_updateNewEncryptedMessage(); - } break; - - case mtpc_updateEncryptedChatTyping: { - auto &d = update.c_updateEncryptedChatTyping(); - } break; - - case mtpc_updateEncryption: { - auto &d = update.c_updateEncryption(); - } break; - - case mtpc_updateEncryptedMessagesRead: { - auto &d = update.c_updateEncryptedMessagesRead(); - } break; - - case mtpc_updatePhoneCall: { - session().calls().handleUpdate(update.c_updatePhoneCall()); - } break; - - case mtpc_updateUserBlocked: { - const auto &d = update.c_updateUserBlocked(); - if (const auto user = session().data().userLoaded(d.vuser_id().v)) { - user->setIsBlocked(mtpIsTrue(d.vblocked())); - } - } break; - - case mtpc_updateServiceNotification: { - const auto &d = update.c_updateServiceNotification(); - const auto text = TextWithEntities { - qs(d.vmessage()), - Api::EntitiesFromMTP(&session(), d.ventities().v) - }; - if (IsForceLogoutNotification(d)) { - Core::App().forceLogOut(text); - } else if (d.is_popup()) { - Ui::show(Box(text)); - } else { - session().data().serviceNotification(text, d.vmedia()); - session().data().checkNewAuthorization(); - } - } break; - - case mtpc_updatePrivacy: { - auto &d = update.c_updatePrivacy(); - const auto allChatsLoaded = [&](const MTPVector &ids) { - for (const auto &chatId : ids.v) { - if (!session().data().chatLoaded(chatId.v) - && !session().data().channelLoaded(chatId.v)) { - return false; - } - } - return true; - }; - const auto allLoaded = [&] { - for (const auto &rule : d.vrules().v) { - const auto loaded = rule.match([&]( - const MTPDprivacyValueAllowChatParticipants & data) { - return allChatsLoaded(data.vchats()); - }, [&](const MTPDprivacyValueDisallowChatParticipants & data) { - return allChatsLoaded(data.vchats()); - }, [](auto &&) { return true; }); - if (!loaded) { - return false; - } - } - return true; - }; - if (const auto key = ApiWrap::Privacy::KeyFromMTP(d.vkey().type())) { - if (allLoaded()) { - session().api().handlePrivacyChange(*key, d.vrules()); - } else { - session().api().reloadPrivacy(*key); - } - } - } break; - - case mtpc_updatePinnedDialogs: { - const auto &d = update.c_updatePinnedDialogs(); - const auto folderId = d.vfolder_id().value_or_empty(); - const auto loaded = !folderId - || (session().data().folderLoaded(folderId) != nullptr); - const auto folder = folderId - ? session().data().folder(folderId).get() - : nullptr; - const auto done = [&] { - const auto list = d.vorder(); - if (!list) { - return false; - } - const auto &order = list->v; - const auto notLoaded = [&](const MTPDialogPeer &peer) { - return peer.match([&](const MTPDdialogPeer &data) { - return !session().data().historyLoaded( - peerFromMTP(data.vpeer())); - }, [&](const MTPDdialogPeerFolder &data) { - if (folderId) { - LOG(("API Error: " - "updatePinnedDialogs has nested folders.")); - return true; - } - return !session().data().folderLoaded(data.vfolder_id().v); - }); - }; - const auto allLoaded = ranges::find_if(order, notLoaded) - == order.end(); - if (!allLoaded) { - return false; - } - session().data().applyPinnedChats(folder, order); - return true; - }(); - if (!done) { - session().api().requestPinnedDialogs(folder); - } - if (!loaded) { - session().data().histories().requestDialogEntry(folder); - } - } break; - - case mtpc_updateDialogPinned: { - const auto &d = update.c_updateDialogPinned(); - const auto folderId = d.vfolder_id().value_or_empty(); - const auto folder = folderId - ? session().data().folder(folderId).get() - : nullptr; - const auto done = d.vpeer().match([&](const MTPDdialogPeer &data) { - const auto id = peerFromMTP(data.vpeer()); - if (const auto history = session().data().historyLoaded(id)) { - history->applyPinnedUpdate(d); - return true; - } - DEBUG_LOG(("API Error: " - "pinned chat not loaded for peer %1, folder: %2" - ).arg(id - ).arg(folderId - )); - return false; - }, [&](const MTPDdialogPeerFolder &data) { - if (folderId != 0) { - DEBUG_LOG(("API Error: Nested folders updateDialogPinned.")); - return false; - } - const auto id = data.vfolder_id().v; - if (const auto folder = session().data().folderLoaded(id)) { - folder->applyPinnedUpdate(d); - return true; - } - DEBUG_LOG(("API Error: " - "pinned folder not loaded for folderId %1, folder: %2" - ).arg(id - ).arg(folderId - )); - return false; - }); - if (!done) { - session().api().requestPinnedDialogs(folder); - } - } break; - - case mtpc_updateChannel: { - auto &d = update.c_updateChannel(); - if (const auto channel = session().data().channelLoaded(d.vchannel_id().v)) { - channel->inviter = UserId(0); - if (channel->amIn()) { - if (channel->isMegagroup() - && !channel->amCreator() - && !channel->hasAdminRights()) { - channel->updateFullForced(); - } - const auto history = channel->owner().history(channel); - //if (const auto feed = channel->feed()) { // #feed - // feed->requestChatListMessage(); - // if (!feed->unreadCountKnown()) { - // feed->owner().histories().requestDialogEntry(feed); - // } - //} else { - history->requestChatListMessage(); - if (!history->unreadCountKnown()) { - history->owner().histories().requestDialogEntry(history); - } - //} - if (!channel->amCreator()) { - session().api().requestSelfParticipant(channel); - } - } - } - } break; - - case mtpc_updateChannelTooLong: { - const auto &d = update.c_updateChannelTooLong(); - if (const auto channel = session().data().channelLoaded(d.vchannel_id().v)) { - const auto pts = d.vpts(); - if (!pts || channel->pts() < pts->v) { - getChannelDifference(channel); - } - } - } break; - - case mtpc_updateChannelMessageViews: { - auto &d = update.c_updateChannelMessageViews(); - if (auto item = session().data().message(d.vchannel_id().v, d.vid().v)) { - item->setViewsCount(d.vviews().v); - } - } break; - - case mtpc_updateChannelAvailableMessages: { - auto &d = update.c_updateChannelAvailableMessages(); - if (const auto channel = session().data().channelLoaded(d.vchannel_id().v)) { - channel->setAvailableMinId(d.vavailable_min_id().v); - if (const auto history = session().data().historyLoaded(channel)) { - history->clearUpTill(d.vavailable_min_id().v); - } - } - } break; - - // Pinned message. - case mtpc_updateUserPinnedMessage: { - const auto &d = update.c_updateUserPinnedMessage(); - if (const auto user = session().data().userLoaded(d.vuser_id().v)) { - user->setPinnedMessageId(d.vid().v); - } - } break; - - case mtpc_updateChatPinnedMessage: { - const auto &d = update.c_updateChatPinnedMessage(); - if (const auto chat = session().data().chatLoaded(d.vchat_id().v)) { - const auto status = chat->applyUpdateVersion(d.vversion().v); - if (status == ChatData::UpdateStatus::Good) { - chat->setPinnedMessageId(d.vid().v); - } - } - } break; - - case mtpc_updateChannelPinnedMessage: { - const auto &d = update.c_updateChannelPinnedMessage(); - if (const auto channel = session().data().channelLoaded(d.vchannel_id().v)) { - channel->setPinnedMessageId(d.vid().v); - } - } break; - - ////// Cloud sticker sets - case mtpc_updateNewStickerSet: { - const auto &d = update.c_updateNewStickerSet(); - session().data().stickers().newSetReceived(d.vstickerset()); - } break; - - case mtpc_updateStickerSetsOrder: { - auto &d = update.c_updateStickerSetsOrder(); - if (!d.is_masks()) { - const auto &order = d.vorder().v; - const auto &sets = session().data().stickers().sets(); - Data::StickersSetsOrder result; - for (const auto &item : order) { - if (sets.find(item.v) == sets.cend()) { - break; - } - result.push_back(item.v); - } - if (result.size() != session().data().stickers().setsOrder().size() - || result.size() != order.size()) { - session().data().stickers().setLastUpdate(0); - session().api().updateStickers(); - } else { - session().data().stickers().setsOrderRef() = std::move(result); - session().local().writeInstalledStickers(); - session().data().stickers().notifyUpdated(); - } - } - } break; - - case mtpc_updateStickerSets: { - session().data().stickers().setLastUpdate(0); - session().api().updateStickers(); - } break; - - case mtpc_updateRecentStickers: { - session().data().stickers().setLastRecentUpdate(0); - session().api().updateStickers(); - } break; - - case mtpc_updateFavedStickers: { - session().data().stickers().setLastFavedUpdate(0); - session().api().updateStickers(); - } break; - - case mtpc_updateReadFeaturedStickers: { - // We read some of the featured stickers, perhaps not all of them. - // Here we don't know what featured sticker sets were read, so we - // request all of them once again. - session().data().stickers().setLastFeaturedUpdate(0); - session().api().updateStickers(); - } break; - - ////// Cloud saved GIFs - case mtpc_updateSavedGifs: { - session().data().stickers().setLastSavedGifsUpdate(0); - session().api().updateStickers(); - } break; - - ////// Cloud drafts - case mtpc_updateDraftMessage: { - const auto &data = update.c_updateDraftMessage(); - const auto peerId = peerFromMTP(data.vpeer()); - data.vdraft().match([&](const MTPDdraftMessage &data) { - Data::ApplyPeerCloudDraft(&session(), peerId, data); - }, [&](const MTPDdraftMessageEmpty &data) { - Data::ClearPeerCloudDraft( - &session(), - peerId, - data.vdate().value_or_empty()); - }); - } break; - - ////// Cloud langpacks - case mtpc_updateLangPack: { - const auto &data = update.c_updateLangPack(); - Lang::CurrentCloudManager().applyLangPackDifference(data.vdifference()); - } break; - - case mtpc_updateLangPackTooLong: { - const auto &data = update.c_updateLangPackTooLong(); - const auto code = qs(data.vlang_code()); - if (!code.isEmpty()) { - Lang::CurrentCloudManager().requestLangPackDifference(code); - } - } break; - - ////// Cloud themes - case mtpc_updateTheme: { - const auto &data = update.c_updateTheme(); - session().data().cloudThemes().applyUpdate(data.vtheme()); - } break; - - } +void MainWidget::saveFieldToHistoryLocalDraft() { + _history->saveFieldToHistoryLocalDraft(); } namespace App { diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index a90a94914..c41c3acc8 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -111,6 +111,7 @@ public: MainWidget( QWidget *parent, not_null controller); + ~MainWidget(); [[nodiscard]] Main::Session &session() const; [[nodiscard]] not_null controller() const; @@ -118,8 +119,6 @@ public: [[nodiscard]] bool isMainSectionShown() const; [[nodiscard]] bool isThirdSectionShown() const; - [[nodiscard]] int contentScrollAddToY() const; - void returnTabbedSelector(); void showAnimated(const QPixmap &bgAnimCache, bool back = false); @@ -146,10 +145,6 @@ public: void windowShown(); - void sentUpdatesReceived(uint64 randomId, const MTPUpdates &updates); - void sentUpdatesReceived(const MTPUpdates &updates) { - return sentUpdatesReceived(0, updates); - } void historyToDown(History *hist); void dialogsToUp(); void checkHistoryActivation(); @@ -174,16 +169,11 @@ public: const std::optional &oldId); bool onSendSticker(DocumentData *sticker); - void updateOnlineDisplayIn(int32 msecs); - bool isActive() const; [[nodiscard]] bool doWeMarkAsRead() const; - bool lastWasOnline() const; - crl::time lastSetOnline() const; - void saveDraftToCloud(); void applyCloudDraft(History *history); - void writeDrafts(History *history); + void saveFieldToHistoryLocalDraft(); int32 dlgsWidth() const; @@ -222,10 +212,6 @@ public: void searchMessages(const QString &query, Dialogs::Key inChat); void itemEdited(not_null item); - void checkLastUpdate(bool afterSleep); - - bool isIdle() const; - QPixmap cachedBackground(const QRect &forRect, int &x, int &y); void updateScrollColors(); @@ -248,31 +234,13 @@ public: void choosePeer(PeerId peerId, MsgId showAtMsgId); void clearBotStartToken(PeerData *peer); - void ptsWaiterStartTimerFor(ChannelData *channel, int32 ms); // ms <= 0 - stop timer - void feedUpdates(const MTPUpdates &updates, uint64 randomId = 0); - void ctrlEnterSubmitUpdated(); void setInnerFocus(); void scheduleViewIncrement(HistoryItem *item); - void feedChannelDifference(const MTPDupdates_channelDifference &data); - - // Made public for ApiWrap, while it is still here. - // Better would be for this to be moved to ApiWrap. - bool requestingDifference() const { - return _ptsWaiter.requesting(); - } - void getDifference(); - void updateOnline(bool gotOtherOffline = false); - void checkIdleFinish(); - bool contentOverlapped(const QRect &globalRect); - bool ptsUpdateAndApply(int32 pts, int32 ptsCount, const MTPUpdates &updates); - bool ptsUpdateAndApply(int32 pts, int32 ptsCount, const MTPUpdate &update); - bool ptsUpdateAndApply(int32 pts, int32 ptsCount); - void searchInChat(Dialogs::Key chat); void app_sendBotCallback( @@ -293,10 +261,6 @@ public: void closeBothPlayers(); - bool isQuitPrevent(); - - ~MainWidget(); - signals: void dialogsUpdated(); @@ -313,27 +277,7 @@ protected: bool eventFilter(QObject *o, QEvent *e) override; private: - using ChannelGetDifferenceTime = QMap; - enum class ChannelDifferenceRequest { - Unknown, - PtsGapOrShortPoll, - AfterFail, - }; - - struct DeleteHistoryRequest { - PeerData *peer; - bool justClearHistory; - }; - - struct DeleteAllFromUserParams { - ChannelData *channel; - UserData *from; - }; - void viewsIncrement(); - void sendPing(); - void getDifferenceByPts(); - void getDifferenceAfterFail(); void animationCallback(); void handleAdaptiveLayoutUpdate(); @@ -349,7 +293,6 @@ private: [[nodiscard]] bool saveThirdSectionToStackBack() const; [[nodiscard]] auto thirdSectionForCurrentMainSection(Dialogs::Key key) -> std::unique_ptr; - void userIsContactUpdated(not_null user); void setupConnectingWidget(); void createPlayer(); @@ -381,28 +324,6 @@ private: void saveSectionInStack(); - void getChannelDifference( - not_null channel, - ChannelDifferenceRequest from = ChannelDifferenceRequest::Unknown); - void gotDifference(const MTPupdates_Difference &diff); - bool failDifference(const RPCError &e); - void feedDifference(const MTPVector &users, const MTPVector &chats, const MTPVector &msgs, const MTPVector &other); - void gotState(const MTPupdates_State &state); - void updSetState(int32 pts, int32 date, int32 qts, int32 seq); - void gotChannelDifference(ChannelData *channel, const MTPupdates_ChannelDifference &diff); - bool failChannelDifference(ChannelData *channel, const RPCError &err); - void failDifferenceStartTimerFor(ChannelData *channel); - - void mtpUpdateReceived(const MTPUpdates &updates); - void mtpNewSessionCreated(); - void feedUpdateVector( - const MTPVector &updates, - bool skipMessageIds = false); - // Doesn't call sendHistoryChangeNotifications itself. - void feedMessageIds(const MTPVector &updates); - // Doesn't call sendHistoryChangeNotifications itself. - void feedUpdate(const MTPUpdate &update); - void usernameResolveDone(QPair msgIdAndStartToken, const MTPcontacts_ResolvedPeer &result); bool usernameResolveFail(QString name, const RPCError &error); @@ -427,14 +348,9 @@ private: bool floatPlayerIsVisible(not_null item) override; void floatPlayerClosed(FullMsgId itemId); - bool getDifferenceTimeChanged(ChannelData *channel, int32 ms, ChannelGetDifferenceTime &channelCurTime, crl::time &curTime); - void viewsIncrementDone(QVector ids, const MTPVector &result, mtpRequestId req); bool viewsIncrementFail(const RPCError &error, mtpRequestId req); - void updateStatusDone(const MTPBool &result); - bool updateStatusFail(const RPCError &error); - void refreshResizeAreas(); template void createResizeArea( @@ -498,38 +414,6 @@ private: int _exportTopBarHeight = 0; int _contentScrollAddToY = 0; - int32 updDate = 0; - int32 updQts = -1; - int32 updSeq = 0; - base::Timer _noUpdatesTimer; - - PtsWaiter _ptsWaiter; - - ChannelGetDifferenceTime _channelGetDifferenceTimeByPts, _channelGetDifferenceTimeAfterFail; - crl::time _getDifferenceTimeByPts = 0; - crl::time _getDifferenceTimeAfterFail = 0; - - base::Timer _byPtsTimer; - - QMap _bySeqUpdates; - base::Timer _bySeqTimer; - - base::Timer _byMinChannelTimer; - - mtpRequestId _onlineRequest = 0; - base::Timer _onlineTimer; - base::Timer _idleFinishTimer; - bool _lastWasOnline = false; - crl::time _lastSetOnline = 0; - bool _isIdle = false; - - int32 _failDifferenceTimeout = 1; // growing timeout for getDifference calls, if it fails - QMap _channelFailDifferenceTimeout; // growing timeout for getChannelDifference calls, if it fails - base::Timer _failDifferenceTimer; - - crl::time _lastUpdateTime = 0; - bool _handlingChannelDifference = false; - QPixmap _cachedBackground; QRect _cachedFor, _willCacheFor; int _cachedX = 0; diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp index 0ea02edc4..097197b18 100644 --- a/Telegram/SourceFiles/mainwindow.cpp +++ b/Telegram/SourceFiles/mainwindow.cpp @@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/storage_account.h" #include "storage/localstorage.h" #include "apiwrap.h" +#include "api/api_updates.h" #include "settings/settings_intro.h" #include "platform/platform_notifications_manager.h" #include "base/platform/base_platform_info.h" @@ -556,9 +557,12 @@ bool MainWindow::eventFilter(QObject *object, QEvent *e) { case QEvent::MouseMove: { const auto position = static_cast(e)->globalPos(); - if (_main && _main->isIdle() && _lastMousePosition != position) { - Core::App().updateNonIdle(); - _main->checkIdleFinish(); + if (_lastMousePosition != position) { + if (const auto controller = sessionController()) { + if (controller->session().updates().isIdle()) { + Core::App().updateNonIdle(); + } + } } _lastMousePosition = position; } break; @@ -1007,7 +1011,9 @@ void MainWindow::sendPaths() { } void MainWindow::updateIsActiveHook() { - if (_main) _main->updateOnline(); + if (const auto controller = sessionController()) { + controller->session().updates().updateOnline(); + } } MainWindow::~MainWindow() { diff --git a/Telegram/SourceFiles/mtproto/sender.h b/Telegram/SourceFiles/mtproto/sender.h index 6d91b2914..da96ff478 100644 --- a/Telegram/SourceFiles/mtproto/sender.h +++ b/Telegram/SourceFiles/mtproto/sender.h @@ -271,7 +271,9 @@ public: public: void cancel() { - _sender->senderRequestCancel(_requestId); + if (_requestId) { + _sender->senderRequestCancel(_requestId); + } } private: diff --git a/Telegram/SourceFiles/settings/settings_codes.cpp b/Telegram/SourceFiles/settings/settings_codes.cpp index 57a279eb6..18dcee73f 100644 --- a/Telegram/SourceFiles/settings/settings_codes.cpp +++ b/Telegram/SourceFiles/settings/settings_codes.cpp @@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "media/audio/media_audio_track.h" #include "settings/settings_common.h" +#include "api/api_updates.h" #include "facades.h" namespace Settings { @@ -87,8 +88,8 @@ auto GenerateCodes() { })); }); codes.emplace(qsl("getdifference"), [](SessionController *window) { - if (auto main = App::main()) { - main->getDifference(); + if (window) { + window->session().updates().getDifference(); } }); codes.emplace(qsl("loadcolors"), [](SessionController *window) { diff --git a/Telegram/SourceFiles/storage/storage_account.cpp b/Telegram/SourceFiles/storage/storage_account.cpp index bb1ee9ed7..73fe9a7f7 100644 --- a/Telegram/SourceFiles/storage/storage_account.cpp +++ b/Telegram/SourceFiles/storage/storage_account.cpp @@ -968,6 +968,35 @@ void Account::readMtpData() { applyReadContext(std::move(context)); } +void Account::writeDrafts(not_null history) { + Storage::MessageDraft storedLocalDraft, storedEditDraft; + MessageCursor localCursor, editCursor; + if (const auto localDraft = history->localDraft()) { + if (_owner->session().supportMode() + || !Data::draftsAreEqual(localDraft, history->cloudDraft())) { + storedLocalDraft = Storage::MessageDraft{ + localDraft->msgId, + localDraft->textWithTags, + localDraft->previewCancelled + }; + localCursor = localDraft->cursor; + } + } + if (const auto editDraft = history->editDraft()) { + storedEditDraft = Storage::MessageDraft{ + editDraft->msgId, + editDraft->textWithTags, + editDraft->previewCancelled + }; + editCursor = editDraft->cursor; + } + writeDrafts( + history->peer->id, + storedLocalDraft, + storedEditDraft); + writeDraftCursors(history->peer->id, localCursor, editCursor); +} + void Account::writeDrafts( const PeerId &peer, const MessageDraft &localDraft, diff --git a/Telegram/SourceFiles/storage/storage_account.h b/Telegram/SourceFiles/storage/storage_account.h index 25b40d665..2473b04f0 100644 --- a/Telegram/SourceFiles/storage/storage_account.h +++ b/Telegram/SourceFiles/storage/storage_account.h @@ -71,6 +71,7 @@ public: void writeBackground(const Data::WallPaper &paper, const QImage &image); bool readBackground(); + void writeDrafts(not_null history); void writeDrafts( const PeerId &peer, const MessageDraft &localDraft, diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp index ac06bddb5..c00167259 100644 --- a/Telegram/SourceFiles/window/notifications_manager.cpp +++ b/Telegram/SourceFiles/window/notifications_manager.cpp @@ -21,7 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "core/application.h" #include "mainwindow.h" -#include "mainwidget.h" +#include "api/api_updates.h" #include "apiwrap.h" #include "main/main_session.h" #include "facades.h" @@ -120,9 +120,10 @@ void System::schedule(not_null item) { auto delay = item->Has() ? 500 : 100; const auto t = base::unixtime::now(); const auto ms = crl::now(); - const bool isOnline = App::main()->lastWasOnline(); + const auto &updates = history->session().updates(); + const bool isOnline = updates.lastWasOnline(); const auto otherNotOld = ((cOtherOnline() * 1000LL) + Global::OnlineCloudTimeout() > t * 1000LL); - const bool otherLaterThanMe = (cOtherOnline() * 1000LL + (ms - App::main()->lastSetOnline()) > t * 1000LL); + const bool otherLaterThanMe = (cOtherOnline() * 1000LL + (ms - updates.lastSetOnline()) > t * 1000LL); if (!isOnline && otherNotOld && otherLaterThanMe) { delay = Global::NotifyCloudDelay(); } else if (cOtherOnline() >= t) { diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index bf2bf67dc..1a9861260 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -144,6 +144,8 @@ SessionController::SessionController( refreshFiltersMenu(); }); }, session->lifetime()); + + session->addWindow(this); } not_null<::MainWindow*> SessionController::widget() const {