1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4 
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "data/data_session.h"
9 
10 #include "main/main_session.h"
11 #include "main/main_session_settings.h"
12 #include "main/main_account.h"
13 #include "apiwrap.h"
14 #include "mainwidget.h"
15 #include "api/api_text_entities.h"
16 #include "core/application.h"
17 #include "core/mime_type.h" // Core::IsMimeSticker
18 #include "core/crash_reports.h" // CrashReports::SetAnnotation
19 #include "ui/image/image.h"
20 #include "ui/image/image_location_factory.h" // Images::FromPhotoSize
21 #include "ui/text/format_values.h" // Ui::FormatPhone
22 #include "export/export_manager.h"
23 #include "export/view/export_view_panel_controller.h"
24 #include "mtproto/mtproto_config.h"
25 #include "window/notifications_manager.h"
26 #include "history/history.h"
27 #include "history/history_item_components.h"
28 #include "history/view/media/history_view_media.h"
29 #include "history/view/history_view_element.h"
30 #include "inline_bots/inline_bot_layout_item.h"
31 #include "storage/storage_account.h"
32 #include "storage/storage_encrypted_file.h"
33 #include "media/player/media_player_instance.h" // instance()->play()
34 #include "media/audio/media_audio.h"
35 #include "boxes/abstract_box.h"
36 #include "passport/passport_form_controller.h"
37 #include "lang/lang_keys.h" // tr::lng_deleted(tr::now) in user name
38 #include "data/stickers/data_stickers.h"
39 #include "data/data_changes.h"
40 #include "data/data_group_call.h"
41 #include "data/data_media_types.h"
42 #include "data/data_folder.h"
43 #include "data/data_channel.h"
44 #include "data/data_chat.h"
45 #include "data/data_user.h"
46 #include "data/data_file_origin.h"
47 #include "data/data_photo.h"
48 #include "data/data_document.h"
49 #include "data/data_web_page.h"
50 #include "data/data_wall_paper.h"
51 #include "data/data_game.h"
52 #include "data/data_poll.h"
53 #include "data/data_chat_filters.h"
54 #include "data/data_scheduled_messages.h"
55 #include "data/data_send_action.h"
56 #include "data/data_sponsored_messages.h"
57 #include "data/data_cloud_themes.h"
58 #include "data/data_streaming.h"
59 #include "data/data_media_rotation.h"
60 #include "data/data_histories.h"
61 #include "base/platform/base_platform_info.h"
62 #include "base/unixtime.h"
63 #include "base/call_delayed.h"
64 #include "base/random.h"
65 #include "facades.h" // Notify::switchInlineBotButtonReceived
66 #include "app.h"
67 #include "styles/style_boxes.h" // st::backgroundSize
68 
69 namespace Data {
70 namespace {
71 
72 constexpr auto kMaxNotifyCheckDelay = 24 * 3600 * crl::time(1000);
73 
74 using ViewElement = HistoryView::Element;
75 
76 // s: box 100x100
77 // m: box 320x320
78 // x: box 800x800
79 // y: box 1280x1280
80 // w: box 2560x2560 // if loading this fix HistoryPhoto::updateFrom
81 // a: crop 160x160
82 // b: crop 320x320
83 // c: crop 640x640
84 // d: crop 1280x1280
85 const auto InlineLevels = "i"_q;
86 const auto SmallLevels = "sa"_q;
87 const auto ThumbnailLevels = "mbsa"_q;
88 const auto LargeLevels = "ydxcwmbsa"_q;
89 
CheckForSwitchInlineButton(not_null<HistoryItem * > item)90 void CheckForSwitchInlineButton(not_null<HistoryItem*> item) {
91 	if (item->out() || !item->hasSwitchInlineButton()) {
92 		return;
93 	}
94 	if (const auto user = item->history()->peer->asUser()) {
95 		if (!user->isBot() || !user->botInfo->inlineReturnTo.key) {
96 			return;
97 		}
98 		if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) {
99 			for (const auto &row : markup->data.rows) {
100 				for (const auto &button : row) {
101 					using ButtonType = HistoryMessageMarkupButton::Type;
102 					if (button.type == ButtonType::SwitchInline) {
103 						Notify::switchInlineBotButtonReceived(
104 							&item->history()->session(),
105 							QString::fromUtf8(button.data));
106 						return;
107 					}
108 				}
109 			}
110 		}
111 	}
112 }
113 
114 // We should get a full restriction in "{full}: {reason}" format and we
115 // need to find an "-all" tag in {full}, otherwise ignore this restriction.
ExtractUnavailableReasons(const QVector<MTPRestrictionReason> & restrictions)116 std::vector<UnavailableReason> ExtractUnavailableReasons(
117 		const QVector<MTPRestrictionReason> &restrictions) {
118 	return ranges::views::all(
119 		restrictions
120 	) | ranges::views::filter([](const MTPRestrictionReason &restriction) {
121 		return restriction.match([&](const MTPDrestrictionReason &data) {
122 			const auto platform = qs(data.vplatform());
123 			return false
124 #ifdef OS_MAC_STORE
125 				|| (platform == qstr("ios"))
126 #elif defined OS_WIN_STORE // OS_MAC_STORE
127 				|| (platform == qstr("ms"))
128 #endif // OS_MAC_STORE || OS_WIN_STORE
129 				|| (platform == qstr("all"));
130 		});
131 	}) | ranges::views::transform([](const MTPRestrictionReason &restriction) {
132 		return restriction.match([&](const MTPDrestrictionReason &data) {
133 			return UnavailableReason{ qs(data.vreason()), qs(data.vtext()) };
134 		});
135 	}) | ranges::to_vector;
136 }
137 
FindInlineThumbnail(const QVector<MTPPhotoSize> & sizes)138 [[nodiscard]] InlineImageLocation FindInlineThumbnail(
139 		const QVector<MTPPhotoSize> &sizes) {
140 	const auto i = ranges::find(
141 		sizes,
142 		mtpc_photoStrippedSize,
143 		&MTPPhotoSize::type);
144 	const auto j = ranges::find(
145 		sizes,
146 		mtpc_photoPathSize,
147 		&MTPPhotoSize::type);
148 	return (i != sizes.end())
149 		? InlineImageLocation{ i->c_photoStrippedSize().vbytes().v, false }
150 		: (j != sizes.end())
151 		? InlineImageLocation{ j->c_photoPathSize().vbytes().v, true }
152 		: InlineImageLocation();
153 }
154 
FindDocumentInlineThumbnail(const MTPDdocument & data)155 [[nodiscard]] InlineImageLocation FindDocumentInlineThumbnail(
156 		const MTPDdocument &data) {
157 	return FindInlineThumbnail(data.vthumbs().value_or_empty());
158 }
159 
FindDocumentThumbnail(const MTPDdocument & data)160 [[nodiscard]] MTPPhotoSize FindDocumentThumbnail(const MTPDdocument &data) {
161 	const auto area = [](const MTPPhotoSize &size) {
162 		static constexpr auto kInvalid = 0;
163 		return size.match([](const MTPDphotoSizeEmpty &) {
164 			return kInvalid;
165 		}, [](const MTPDphotoStrippedSize &) {
166 			return kInvalid;
167 		}, [](const MTPDphotoPathSize &) {
168 			return kInvalid;
169 		}, [](const auto &data) {
170 			return (data.vw().v * data.vh().v);
171 		});
172 	};
173 	const auto thumbs = data.vthumbs();
174 	if (!thumbs) {
175 		return MTP_photoSizeEmpty(MTP_string());
176 	}
177 	const auto &list = thumbs->v;
178 	const auto i = ranges::max_element(list, std::less<>(), area);
179 	return (i != list.end() && area(*i) > 0)
180 		? (*i)
181 		: MTPPhotoSize(MTP_photoSizeEmpty(MTP_string()));
182 }
183 
FindDocumentVideoThumbnail(const MTPDdocument & data)184 [[nodiscard]] std::optional<MTPVideoSize> FindDocumentVideoThumbnail(
185 		const MTPDdocument &data) {
186 	const auto area = [](const MTPVideoSize &size) {
187 		return size.match([](const MTPDvideoSize &data) {
188 			return (data.vw().v * data.vh().v);
189 		});
190 	};
191 	const auto thumbs = data.vvideo_thumbs();
192 	if (!thumbs) {
193 		return std::nullopt;
194 	}
195 	const auto &list = thumbs->v;
196 	const auto i = ranges::max_element(list, std::less<>(), area);
197 	return (i != list.end() && area(*i) > 0)
198 		? std::make_optional(*i)
199 		: std::nullopt;
200 }
201 
FindPhotoInlineThumbnail(const MTPDphoto & data)202 [[nodiscard]] QByteArray FindPhotoInlineThumbnail(const MTPDphoto &data) {
203 	const auto thumbnail = FindInlineThumbnail(data.vsizes().v);
204 	return !thumbnail.isPath ? thumbnail.bytes : QByteArray();
205 }
206 
VideoStartTime(const MTPDvideoSize & data)207 [[nodiscard]] int VideoStartTime(const MTPDvideoSize &data) {
208 	return int(
209 		std::clamp(
210 			std::floor(data.vvideo_start_ts().value_or_empty() * 1000),
211 			0.,
212 			double(std::numeric_limits<int>::max())));
213 }
214 
215 } // namespace
216 
Session(not_null<Main::Session * > session)217 Session::Session(not_null<Main::Session*> session)
218 : _session(session)
219 , _cache(Core::App().databases().get(
220 	_session->local().cachePath(),
221 	_session->local().cacheSettings()))
222 , _bigFileCache(Core::App().databases().get(
223 	_session->local().cacheBigFilePath(),
224 	_session->local().cacheBigFileSettings()))
225 , _chatsList(
226 	session,
227 	FilterId(),
228 	session->serverConfig().pinnedDialogsCountMax.value())
229 , _contactsList(Dialogs::SortMode::Name)
230 , _contactsNoChatsList(Dialogs::SortMode::Name)
231 , _ttlCheckTimer([=] { checkTTLs(); })
__anona11d63740e02null232 , _selfDestructTimer([=] { checkSelfDestructItems(); })
__anona11d63740f02null233 , _pollsClosingTimer([=] { checkPollsClosings(); })
__anona11d63741002null234 , _unmuteByFinishedTimer([=] { unmuteByFinished(); })
235 , _groups(this)
236 , _chatsFilters(std::make_unique<ChatFilters>(this))
237 , _scheduledMessages(std::make_unique<ScheduledMessages>(this))
238 , _cloudThemes(std::make_unique<CloudThemes>(session))
239 , _sendActionManager(std::make_unique<SendActionManager>())
240 , _streaming(std::make_unique<Streaming>(this))
241 , _mediaRotation(std::make_unique<MediaRotation>())
242 , _histories(std::make_unique<Histories>(this))
243 , _stickers(std::make_unique<Stickers>(this))
244 , _sponsoredMessages(std::make_unique<SponsoredMessages>(this)) {
245 	_cache->open(_session->local().cacheKey());
246 	_bigFileCache->open(_session->local().cacheBigFileKey());
247 
248 	if constexpr (Platform::IsLinux()) {
249 		const auto wasVersion = _session->local().oldMapVersion();
250 		if (wasVersion >= 1007011 && wasVersion < 1007015) {
251 			_bigFileCache->clear();
252 			_cache->clearByTag(Data::kImageCacheTag);
253 		}
254 	}
255 
256 	setupMigrationViewer();
257 	setupChannelLeavingViewer();
258 	setupPeerNameViewer();
259 	setupUserIsContactViewer();
260 
261 	_chatsList.unreadStateChanges(
__anona11d63741102null262 	) | rpl::start_with_next([=] {
263 		notifyUnreadBadgeChanged();
264 	}, _lifetime);
265 
266 	_chatsFilters->changed(
__anona11d63741202null267 	) | rpl::start_with_next([=] {
268 		const auto enabled = !_chatsFilters->list().empty();
269 		if (enabled != session->settings().dialogsFiltersEnabled()) {
270 			session->settings().setDialogsFiltersEnabled(enabled);
271 			session->saveSettingsDelayed();
272 		}
273 	}, _lifetime);
274 
275 }
276 
clear()277 void Session::clear() {
278 	// Optimization: clear notifications before destroying items.
279 	Core::App().notifications().clearFromSession(_session);
280 
281 	_sendActionManager->clear();
282 
283 	_histories->unloadAll();
284 	_scheduledMessages = nullptr;
285 	_sponsoredMessages = nullptr;
286 	_dependentMessages.clear();
287 	base::take(_messages);
288 	base::take(_channelMessages);
289 	_messageByRandomId.clear();
290 	_sentMessagesData.clear();
291 	cSetRecentInlineBots(RecentInlineBots());
292 	cSetRecentStickers(RecentStickerPack());
293 	App::clearMousedItems();
294 	_histories->clearAll();
295 	_webpages.clear();
296 	_locations.clear();
297 	_polls.clear();
298 	_games.clear();
299 	_documents.clear();
300 	_photos.clear();
301 }
302 
keepAlive(std::shared_ptr<PhotoMedia> media)303 void Session::keepAlive(std::shared_ptr<PhotoMedia> media) {
304 	// NB! This allows PhotoMedia to outlive Main::Session!
305 	// In case this is a problem this code should be rewritten.
306 	crl::on_main(&session(), [media = std::move(media)]{});
307 }
308 
keepAlive(std::shared_ptr<DocumentMedia> media)309 void Session::keepAlive(std::shared_ptr<DocumentMedia> media) {
310 	// NB! This allows DocumentMedia to outlive Main::Session!
311 	// In case this is a problem this code should be rewritten.
312 	crl::on_main(&session(), [media = std::move(media)] {});
313 }
314 
peer(PeerId id)315 not_null<PeerData*> Session::peer(PeerId id) {
316 	const auto i = _peers.find(id);
317 	if (i != _peers.cend()) {
318 		return i->second.get();
319 	}
320 	auto result = [&]() -> std::unique_ptr<PeerData> {
321 		if (peerIsUser(id)) {
322 			return std::make_unique<UserData>(this, id);
323 		} else if (peerIsChat(id)) {
324 			return std::make_unique<ChatData>(this, id);
325 		} else if (peerIsChannel(id)) {
326 			return std::make_unique<ChannelData>(this, id);
327 		}
328 		Unexpected("Peer id type.");
329 	}();
330 
331 	result->input = MTPinputPeer(MTP_inputPeerEmpty());
332 	return _peers.emplace(id, std::move(result)).first->second.get();
333 }
334 
user(UserId id)335 not_null<UserData*> Session::user(UserId id) {
336 	return peer(peerFromUser(id))->asUser();
337 }
338 
chat(ChatId id)339 not_null<ChatData*> Session::chat(ChatId id) {
340 	return peer(peerFromChat(id))->asChat();
341 }
342 
channel(ChannelId id)343 not_null<ChannelData*> Session::channel(ChannelId id) {
344 	return peer(peerFromChannel(id))->asChannel();
345 }
346 
peerLoaded(PeerId id) const347 PeerData *Session::peerLoaded(PeerId id) const {
348 	const auto i = _peers.find(id);
349 	if (i == end(_peers)) {
350 		return nullptr;
351 	} else if (!i->second->isLoaded()) {
352 		return nullptr;
353 	}
354 	return i->second.get();
355 }
356 
userLoaded(UserId id) const357 UserData *Session::userLoaded(UserId id) const {
358 	if (const auto peer = peerLoaded(peerFromUser(id))) {
359 		return peer->asUser();
360 	}
361 	return nullptr;
362 }
363 
chatLoaded(ChatId id) const364 ChatData *Session::chatLoaded(ChatId id) const {
365 	if (const auto peer = peerLoaded(peerFromChat(id))) {
366 		return peer->asChat();
367 	}
368 	return nullptr;
369 }
370 
channelLoaded(ChannelId id) const371 ChannelData *Session::channelLoaded(ChannelId id) const {
372 	if (const auto peer = peerLoaded(peerFromChannel(id))) {
373 		return peer->asChannel();
374 	}
375 	return nullptr;
376 }
377 
processUser(const MTPUser & data)378 not_null<UserData*> Session::processUser(const MTPUser &data) {
379 	const auto result = user(data.match([](const auto &data) {
380 		return data.vid().v;
381 	}));
382 	auto minimal = false;
383 	const MTPUserStatus *status = nullptr;
384 	const MTPUserStatus emptyStatus = MTP_userStatusEmpty();
385 
386 	using UpdateFlag = PeerUpdate::Flag;
387 	auto flags = UpdateFlag::None | UpdateFlag::None;
388 	data.match([&](const MTPDuserEmpty &data) {
389 		const auto canShareThisContact = result->canShareThisContactFast();
390 
391 		result->input = MTP_inputPeerUser(data.vid(), MTP_long(0));
392 		result->inputUser = MTP_inputUser(data.vid(), MTP_long(0));
393 		result->setName(tr::lng_deleted(tr::now), QString(), QString(), QString());
394 		result->setPhoto(MTP_userProfilePhotoEmpty());
395 		result->setFlags(UserDataFlag::Deleted);
396 		if (!result->phone().isEmpty()) {
397 			result->setPhone(QString());
398 			flags |= UpdateFlag::PhoneNumber;
399 		}
400 		result->setBotInfoVersion(-1);
401 		status = &emptyStatus;
402 		result->setIsContact(false);
403 		if (canShareThisContact != result->canShareThisContactFast()) {
404 			flags |= UpdateFlag::CanShareContact;
405 		}
406 	}, [&](const MTPDuser &data) {
407 		minimal = data.is_min();
408 
409 		const auto canShareThisContact = result->canShareThisContactFast();
410 
411 		using Flag = UserDataFlag;
412 		const auto flagsMask = Flag::Deleted
413 			| Flag::Verified
414 			| Flag::Scam
415 			| Flag::Fake
416 			| Flag::BotInlineGeo
417 			| Flag::Support
418 			| (!minimal
419 				? Flag::Contact
420 				| Flag::MutualContact
421 				| Flag::DiscardMinPhoto
422 				: Flag());
423 		const auto flagsSet = (data.is_deleted() ? Flag::Deleted : Flag())
424 			| (data.is_verified() ? Flag::Verified : Flag())
425 			| (data.is_scam() ? Flag::Scam : Flag())
426 			| (data.is_fake() ? Flag::Fake : Flag())
427 			| (data.is_bot_inline_geo() ? Flag::BotInlineGeo : Flag())
428 			| (data.is_support() ? Flag::Support : Flag())
429 			| (!minimal
430 				? (data.is_contact() ? Flag::Contact : Flag())
431 				| (data.is_mutual_contact() ? Flag::MutualContact : Flag())
432 				| (data.is_apply_min_photo() ? Flag() : Flag::DiscardMinPhoto)
433 				: Flag());
434 		result->setFlags((result->flags() & ~flagsMask) | flagsSet);
435 		if (minimal) {
436 			if (result->input.type() == mtpc_inputPeerEmpty) {
437 				result->input = MTP_inputPeerUser(
438 					data.vid(),
439 					MTP_long(data.vaccess_hash().value_or_empty()));
440 			}
441 			if (result->inputUser.type() == mtpc_inputUserEmpty) {
442 				result->inputUser = MTP_inputUser(
443 					data.vid(),
444 					MTP_long(data.vaccess_hash().value_or_empty()));
445 			}
446 		} else {
447 			if (data.is_self()) {
448 				result->input = MTP_inputPeerSelf();
449 				result->inputUser = MTP_inputUserSelf();
450 			} else if (const auto accessHash = data.vaccess_hash()) {
451 				result->input = MTP_inputPeerUser(data.vid(), *accessHash);
452 				result->inputUser = MTP_inputUser(data.vid(), *accessHash);
453 			} else {
454 				result->input = MTP_inputPeerUser(data.vid(), MTP_long(result->accessHash()));
455 				result->inputUser = MTP_inputUser(data.vid(), MTP_long(result->accessHash()));
456 			}
457 			if (const auto restriction = data.vrestriction_reason()) {
458 				result->setUnavailableReasons(
459 					ExtractUnavailableReasons(restriction->v));
460 			} else {
461 				result->setUnavailableReasons({});
462 			}
463 		}
464 		if (data.is_deleted()) {
465 			if (!result->phone().isEmpty()) {
466 				result->setPhone(QString());
467 				flags |= UpdateFlag::PhoneNumber;
468 			}
469 			result->setName(tr::lng_deleted(tr::now), QString(), QString(), QString());
470 			result->setPhoto(MTP_userProfilePhotoEmpty());
471 			status = &emptyStatus;
472 		} else {
473 			// apply first_name and last_name from minimal user only if we don't have
474 			// local values for first name and last name already, otherwise skip
475 			bool noLocalName = result->firstName.isEmpty() && result->lastName.isEmpty();
476 			QString fname = (!minimal || noLocalName) ? TextUtilities::SingleLine(qs(data.vfirst_name().value_or_empty())) : result->firstName;
477 			QString lname = (!minimal || noLocalName) ? TextUtilities::SingleLine(qs(data.vlast_name().value_or_empty())) : result->lastName;
478 
479 			QString phone = minimal ? result->phone() : qs(data.vphone().value_or_empty());
480 			QString uname = minimal ? result->username : TextUtilities::SingleLine(qs(data.vusername().value_or_empty()));
481 
482 			const auto phoneChanged = (result->phone() != phone);
483 			if (phoneChanged) {
484 				result->setPhone(phone);
485 				flags |= UpdateFlag::PhoneNumber;
486 			}
487 			const auto nameChanged = (result->firstName != fname)
488 				|| (result->lastName != lname);
489 
490 			auto showPhone = !result->isServiceUser()
491 				&& !data.is_support()
492 				&& !data.is_self()
493 				&& !data.is_contact()
494 				&& !data.is_mutual_contact();
495 			auto showPhoneChanged = !result->isServiceUser()
496 				&& !data.is_self()
497 				&& ((showPhone && result->isContact())
498 					|| (!showPhone
499 						&& !result->isContact()
500 						&& !result->phone().isEmpty()));
501 			if (minimal) {
502 				showPhoneChanged = false;
503 				showPhone = !result->isServiceUser()
504 					&& !result->isContact()
505 					&& !result->phone().isEmpty()
506 					&& (result->id != _session->userPeerId());
507 			}
508 
509 			// see also Serialize::readPeer
510 
511 			const auto pname = (showPhoneChanged || phoneChanged || nameChanged)
512 				? ((showPhone && !phone.isEmpty())
513 					? Ui::FormatPhone(phone)
514 					: QString())
515 				: result->nameOrPhone;
516 
517 			result->setName(fname, lname, pname, uname);
518 			if (!minimal || result->applyMinPhoto()) {
519 				if (const auto photo = data.vphoto()) {
520 					result->setPhoto(*photo);
521 				} else {
522 					result->setPhoto(MTP_userProfilePhotoEmpty());
523 				}
524 			}
525 			if (const auto accessHash = data.vaccess_hash()) {
526 				result->setAccessHash(accessHash->v);
527 			}
528 			status = data.vstatus();
529 		}
530 		if (!minimal) {
531 			if (const auto botInfoVersion = data.vbot_info_version()) {
532 				result->setBotInfoVersion(botInfoVersion->v);
533 				result->botInfo->readsAllHistory = data.is_bot_chat_history();
534 				if (result->botInfo->cantJoinGroups != data.is_bot_nochats()) {
535 					result->botInfo->cantJoinGroups = data.is_bot_nochats();
536 					flags |= UpdateFlag::BotCanBeInvited;
537 				}
538 				if (const auto placeholder = data.vbot_inline_placeholder()) {
539 					result->botInfo->inlinePlaceholder = '_' + qs(*placeholder);
540 				} else {
541 					result->botInfo->inlinePlaceholder = QString();
542 				}
543 			} else {
544 				result->setBotInfoVersion(-1);
545 			}
546 			result->setIsContact(data.is_contact()
547 				|| data.is_mutual_contact());
548 		}
549 
550 		if (canShareThisContact != result->canShareThisContactFast()) {
551 			flags |= UpdateFlag::CanShareContact;
552 		}
553 	});
554 
555 	if (minimal) {
556 		if (!result->isMinimalLoaded()) {
557 			result->setLoadedStatus(PeerData::LoadedStatus::Minimal);
558 		}
559 	} else if (!result->isLoaded()
560 		&& (!result->isSelf() || !result->phone().isEmpty())) {
561 		result->setLoadedStatus(PeerData::LoadedStatus::Normal);
562 	}
563 
564 	if (status && !minimal) {
565 		const auto oldOnlineTill = result->onlineTill;
566 		const auto newOnlineTill = ApiWrap::OnlineTillFromStatus(
567 			*status,
568 			oldOnlineTill);
569 		if (oldOnlineTill != newOnlineTill) {
570 			result->onlineTill = newOnlineTill;
571 			flags |= UpdateFlag::OnlineStatus;
572 		}
573 	}
574 
575 	if (flags) {
576 		session().changes().peerUpdated(result, flags);
577 	}
578 	return result;
579 }
580 
processChat(const MTPChat & data)581 not_null<PeerData*> Session::processChat(const MTPChat &data) {
582 	const auto result = data.match([&](const MTPDchat &data) {
583 		return peer(peerFromChat(data.vid().v));
584 	}, [&](const MTPDchatForbidden &data) {
585 		return peer(peerFromChat(data.vid().v));
586 	}, [&](const MTPDchatEmpty &data) {
587 		return peer(peerFromChat(data.vid().v));
588 	}, [&](const MTPDchannel &data) {
589 		return peer(peerFromChannel(data.vid().v));
590 	}, [&](const MTPDchannelForbidden &data) {
591 		return peer(peerFromChannel(data.vid().v));
592 	});
593 	auto minimal = false;
594 
595 	using UpdateFlag = Data::PeerUpdate::Flag;
596 	auto flags = UpdateFlag::None | UpdateFlag::None;
597 	data.match([&](const MTPDchat &data) {
598 		const auto chat = result->asChat();
599 
600 		const auto canAddMembers = chat->canAddMembers();
601 		if (chat->version() < data.vversion().v) {
602 			chat->setVersion(data.vversion().v);
603 			chat->invalidateParticipants();
604 		}
605 
606 		chat->input = MTP_inputPeerChat(data.vid());
607 		chat->setName(qs(data.vtitle()));
608 		chat->setPhoto(data.vphoto());
609 		chat->date = data.vdate().v;
610 
611 		if (const auto rights = data.vadmin_rights()) {
612 			chat->setAdminRights(ChatAdminRightsInfo(*rights).flags);
613 		} else {
614 			chat->setAdminRights(ChatAdminRights());
615 		}
616 		if (const auto rights = data.vdefault_banned_rights()) {
617 			chat->setDefaultRestrictions(ChatRestrictionsInfo(*rights).flags);
618 		} else {
619 			chat->setDefaultRestrictions(ChatRestrictions());
620 		}
621 
622 		if (const auto migratedTo = data.vmigrated_to()) {
623 			migratedTo->match([&](const MTPDinputChannel &input) {
624 				const auto channel = this->channel(input.vchannel_id().v);
625 				channel->addFlags(ChannelDataFlag::Megagroup);
626 				if (!channel->access) {
627 					channel->setAccessHash(input.vaccess_hash().v);
628 				}
629 				ApplyMigration(chat, channel);
630 			}, [](const MTPDinputChannelFromMessage &) {
631 				LOG(("API Error: "
632 					"migrated_to contains channel from message."));
633 			}, [](const MTPDinputChannelEmpty &) {
634 			});
635 		}
636 
637 		using Flag = ChatDataFlag;
638 		const auto flagsMask = Flag::Left
639 			| Flag::Kicked
640 			| Flag::Creator
641 			| Flag::Deactivated
642 			| Flag::Forbidden
643 			| Flag::CallActive
644 			| Flag::CallNotEmpty;
645 		const auto flagsSet = (data.is_left() ? Flag::Left : Flag())
646 			| (data.is_kicked() ? Flag::Kicked : Flag())
647 			| (data.is_creator() ? Flag::Creator : Flag())
648 			| (data.is_deactivated() ? Flag::Deactivated : Flag())
649 			| (data.is_call_active() ? Flag::CallActive : Flag())
650 			| ((data.is_call_not_empty()
651 				|| (chat->groupCall()
652 					&& chat->groupCall()->fullCount() > 0))
653 				? Flag::CallNotEmpty
654 				: Flag());
655 		chat->setFlags((chat->flags() & ~flagsMask) | flagsSet);
656 		chat->count = data.vparticipants_count().v;
657 
658 		if (canAddMembers != chat->canAddMembers()) {
659 			flags |= UpdateFlag::Rights;
660 		}
661 	}, [&](const MTPDchatForbidden &data) {
662 		const auto chat = result->asChat();
663 
664 		const auto canAddMembers = chat->canAddMembers();
665 
666 		chat->input = MTP_inputPeerChat(data.vid());
667 		chat->setName(qs(data.vtitle()));
668 		chat->setPhoto(MTP_chatPhotoEmpty());
669 		chat->date = 0;
670 		chat->count = -1;
671 		chat->invalidateParticipants();
672 		chat->setFlags(ChatDataFlag::Forbidden);
673 		chat->setAdminRights(ChatAdminRights());
674 		chat->setDefaultRestrictions(ChatRestrictions());
675 
676 		if (canAddMembers != chat->canAddMembers()) {
677 			flags |= UpdateFlag::Rights;
678 		}
679 	}, [&](const MTPDchannel &data) {
680 		const auto channel = result->asChannel();
681 
682 		minimal = data.is_min();
683 		if (minimal && !result->isLoaded()) {
684 			LOG(("API Warning: not loaded minimal channel applied."));
685 		}
686 
687 		const auto wasInChannel = channel->amIn();
688 		const auto canViewAdmins = channel->canViewAdmins();
689 		const auto canViewMembers = channel->canViewMembers();
690 		const auto canAddMembers = channel->canAddMembers();
691 
692 		if (const auto count = data.vparticipants_count()) {
693 			channel->setMembersCount(count->v);
694 		}
695 		if (const auto rights = data.vdefault_banned_rights()) {
696 			channel->setDefaultRestrictions(ChatRestrictionsInfo(*rights).flags);
697 		} else {
698 			channel->setDefaultRestrictions(ChatRestrictions());
699 		}
700 
701 		if (minimal) {
702 			if (channel->input.type() == mtpc_inputPeerEmpty
703 				|| channel->inputChannel.type() == mtpc_inputChannelEmpty) {
704 				channel->setAccessHash(data.vaccess_hash().value_or_empty());
705 			}
706 		} else {
707 			if (const auto rights = data.vadmin_rights()) {
708 				channel->setAdminRights(ChatAdminRightsInfo(*rights).flags);
709 			} else if (channel->hasAdminRights()) {
710 				channel->setAdminRights(ChatAdminRights());
711 			}
712 			if (const auto rights = data.vbanned_rights()) {
713 				channel->setRestrictions(ChatRestrictionsInfo(*rights));
714 			} else if (channel->hasRestrictions()) {
715 				channel->setRestrictions(ChatRestrictionsInfo());
716 			}
717 			channel->setAccessHash(
718 				data.vaccess_hash().value_or(channel->access));
719 			channel->date = data.vdate().v;
720 			if (const auto restriction = data.vrestriction_reason()) {
721 				channel->setUnavailableReasons(
722 					ExtractUnavailableReasons(restriction->v));
723 			} else {
724 				channel->setUnavailableReasons({});
725 			}
726 		}
727 
728 		using Flag = ChannelDataFlag;
729 		const auto flagsMask = Flag::Broadcast
730 			| Flag::Verified
731 			| Flag::Scam
732 			| Flag::Fake
733 			| Flag::Megagroup
734 			| Flag::Gigagroup
735 			| Flag::Username
736 			| Flag::Signatures
737 			| Flag::HasLink
738 			| Flag::SlowmodeEnabled
739 			| Flag::CallActive
740 			| Flag::CallNotEmpty
741 			| Flag::Forbidden
742 			| (!minimal
743 				? Flag::Left
744 				| Flag::Creator
745 				: Flag());
746 		const auto flagsSet = (data.is_broadcast() ? Flag::Broadcast : Flag())
747 			| (data.is_verified() ? Flag::Verified : Flag())
748 			| (data.is_scam() ? Flag::Scam : Flag())
749 			| (data.is_fake() ? Flag::Fake : Flag())
750 			| (data.is_megagroup() ? Flag::Megagroup : Flag())
751 			| (data.is_gigagroup() ? Flag::Gigagroup : Flag())
752 			| (data.vusername() ? Flag::Username : Flag())
753 			| (data.is_signatures() ? Flag::Signatures : Flag())
754 			| (data.is_has_link() ? Flag::HasLink : Flag())
755 			| (data.is_slowmode_enabled() ? Flag::SlowmodeEnabled : Flag())
756 			| (data.is_call_active() ? Flag::CallActive : Flag())
757 			| ((data.is_call_not_empty()
758 				|| (channel->groupCall()
759 					&& channel->groupCall()->fullCount() > 0))
760 				? Flag::CallNotEmpty
761 				: Flag())
762 			| (!minimal
763 				? (data.is_left() ? Flag::Left : Flag())
764 				| (data.is_creator() ? Flag::Creator : Flag())
765 				: Flag());
766 		channel->setFlags((channel->flags() & ~flagsMask) | flagsSet);
767 
768 		channel->setName(
769 			qs(data.vtitle()),
770 			TextUtilities::SingleLine(qs(data.vusername().value_or_empty())));
771 
772 		channel->setPhoto(data.vphoto());
773 
774 		if (wasInChannel != channel->amIn()) {
775 			flags |= UpdateFlag::ChannelAmIn;
776 		}
777 		if (canViewAdmins != channel->canViewAdmins()
778 			|| canViewMembers != channel->canViewMembers()
779 			|| canAddMembers != channel->canAddMembers()) {
780 			flags |= UpdateFlag::Rights;
781 		}
782 	}, [&](const MTPDchannelForbidden &data) {
783 		const auto channel = result->asChannel();
784 
785 		auto wasInChannel = channel->amIn();
786 		auto canViewAdmins = channel->canViewAdmins();
787 		auto canViewMembers = channel->canViewMembers();
788 		auto canAddMembers = channel->canAddMembers();
789 
790 		using Flag = ChannelDataFlag;
791 		const auto flagsMask = Flag::Broadcast
792 			| Flag::Megagroup
793 			| Flag::Forbidden;
794 		const auto flagsSet = (data.is_broadcast() ? Flag::Broadcast : Flag())
795 			| (data.is_megagroup() ? Flag::Megagroup : Flag())
796 			| Flag::Forbidden;
797 		channel->setFlags((channel->flags() & ~flagsMask) | flagsSet);
798 
799 		if (channel->hasAdminRights()) {
800 			channel->setAdminRights(ChatAdminRights());
801 		}
802 		if (channel->hasRestrictions()) {
803 			channel->setRestrictions(ChatRestrictionsInfo());
804 		}
805 
806 		channel->setName(qs(data.vtitle()), QString());
807 
808 		channel->setAccessHash(data.vaccess_hash().v);
809 		channel->setPhoto(MTP_chatPhotoEmpty());
810 		channel->date = 0;
811 		channel->setMembersCount(0);
812 
813 		if (wasInChannel != channel->amIn()) {
814 			flags |= UpdateFlag::ChannelAmIn;
815 		}
816 		if (canViewAdmins != channel->canViewAdmins()
817 			|| canViewMembers != channel->canViewMembers()
818 			|| canAddMembers != channel->canAddMembers()) {
819 			flags |= UpdateFlag::Rights;
820 		}
821 	}, [](const MTPDchatEmpty &) {
822 	});
823 
824 	if (minimal) {
825 		if (!result->isMinimalLoaded()) {
826 			result->setLoadedStatus(PeerData::LoadedStatus::Minimal);
827 		}
828 	} else if (!result->isLoaded()) {
829 		result->setLoadedStatus(PeerData::LoadedStatus::Normal);
830 	}
831 	if (flags) {
832 		session().changes().peerUpdated(result, flags);
833 	}
834 	return result;
835 }
836 
processUsers(const MTPVector<MTPUser> & data)837 UserData *Session::processUsers(const MTPVector<MTPUser> &data) {
838 	auto result = (UserData*)nullptr;
839 	for (const auto &user : data.v) {
840 		result = processUser(user);
841 	}
842 	return result;
843 }
844 
processChats(const MTPVector<MTPChat> & data)845 PeerData *Session::processChats(const MTPVector<MTPChat> &data) {
846 	auto result = (PeerData*)nullptr;
847 	for (const auto &chat : data.v) {
848 		result = processChat(chat);
849 	}
850 	return result;
851 }
852 
applyMaximumChatVersions(const MTPVector<MTPChat> & data)853 void Session::applyMaximumChatVersions(const MTPVector<MTPChat> &data) {
854 	for (const auto &chat : data.v) {
855 		chat.match([&](const MTPDchat &data) {
856 			if (const auto chat = chatLoaded(data.vid().v)) {
857 				if (data.vversion().v < chat->version()) {
858 					chat->setVersion(data.vversion().v);
859 				}
860 			}
861 		}, [](const auto &) {
862 		});
863 	}
864 }
865 
registerGroupCall(not_null<GroupCall * > call)866 void Session::registerGroupCall(not_null<GroupCall*> call) {
867 	_groupCalls.emplace(call->id(), call);
868 }
869 
unregisterGroupCall(not_null<GroupCall * > call)870 void Session::unregisterGroupCall(not_null<GroupCall*> call) {
871 	_groupCalls.remove(call->id());
872 }
873 
groupCall(CallId callId) const874 GroupCall *Session::groupCall(CallId callId) const {
875 	const auto i = _groupCalls.find(callId);
876 	return (i != end(_groupCalls)) ? i->second.get() : nullptr;
877 }
878 
invitedToCallUsers(CallId callId) const879 auto Session::invitedToCallUsers(CallId callId) const
880 -> const base::flat_set<not_null<UserData*>> & {
881 	static const base::flat_set<not_null<UserData*>> kEmpty;
882 	const auto i = _invitedToCallUsers.find(callId);
883 	return (i != _invitedToCallUsers.end()) ? i->second : kEmpty;
884 }
885 
registerInvitedToCallUser(CallId callId,not_null<PeerData * > peer,not_null<UserData * > user)886 void Session::registerInvitedToCallUser(
887 		CallId callId,
888 		not_null<PeerData*> peer,
889 		not_null<UserData*> user) {
890 	const auto call = peer->groupCall();
891 	if (call && call->id() == callId) {
892 		const auto inCall = ranges::contains(
893 			call->participants(),
894 			user,
895 			&Data::GroupCallParticipant::peer);
896 		if (inCall) {
897 			return;
898 		}
899 	}
900 	_invitedToCallUsers[callId].emplace(user);
901 	_invitesToCalls.fire({ callId, user });
902 }
903 
unregisterInvitedToCallUser(CallId callId,not_null<UserData * > user)904 void Session::unregisterInvitedToCallUser(
905 		CallId callId,
906 		not_null<UserData*> user) {
907 	const auto i = _invitedToCallUsers.find(callId);
908 	if (i != _invitedToCallUsers.end()) {
909 		i->second.remove(user);
910 		if (i->second.empty()) {
911 			_invitedToCallUsers.erase(i);
912 		}
913 	}
914 }
915 
peerByUsername(const QString & username) const916 PeerData *Session::peerByUsername(const QString &username) const {
917 	const auto uname = username.trimmed();
918 	for (const auto &[peerId, peer] : _peers) {
919 		if (!peer->userName().compare(uname, Qt::CaseInsensitive)) {
920 			return peer.get();
921 		}
922 	}
923 	return nullptr;
924 }
925 
enumerateUsers(Fn<void (not_null<UserData * >)> action) const926 void Session::enumerateUsers(Fn<void(not_null<UserData*>)> action) const {
927 	for (const auto &[peerId, peer] : _peers) {
928 		if (const auto user = peer->asUser()) {
929 			action(user);
930 		}
931 	}
932 }
933 
enumerateGroups(Fn<void (not_null<PeerData * >)> action) const934 void Session::enumerateGroups(Fn<void(not_null<PeerData*>)> action) const {
935 	for (const auto &[peerId, peer] : _peers) {
936 		if (peer->isChat() || peer->isMegagroup()) {
937 			action(peer.get());
938 		}
939 	}
940 }
941 
enumerateChannels(Fn<void (not_null<ChannelData * >)> action) const942 void Session::enumerateChannels(
943 		Fn<void(not_null<ChannelData*>)> action) const {
944 	for (const auto &[peerId, peer] : _peers) {
945 		if (const auto channel = peer->asChannel()) {
946 			if (!channel->isMegagroup()) {
947 				action(channel);
948 			}
949 		}
950 	}
951 }
952 
history(PeerId peerId)953 not_null<History*> Session::history(PeerId peerId) {
954 	return _histories->findOrCreate(peerId);
955 }
956 
historyLoaded(PeerId peerId) const957 History *Session::historyLoaded(PeerId peerId) const {
958 	return _histories->find(peerId);
959 }
960 
history(not_null<const PeerData * > peer)961 not_null<History*> Session::history(not_null<const PeerData*> peer) {
962 	return history(peer->id);
963 }
964 
historyLoaded(const PeerData * peer)965 History *Session::historyLoaded(const PeerData *peer) {
966 	return peer ? historyLoaded(peer->id) : nullptr;
967 }
968 
deleteConversationLocally(not_null<PeerData * > peer)969 void Session::deleteConversationLocally(not_null<PeerData*> peer) {
970 	const auto history = historyLoaded(peer);
971 	if (history) {
972 		if (history->folderKnown()) {
973 			setChatPinned(history, FilterId(), false);
974 		}
975 		removeChatListEntry(history);
976 		history->clear(peer->isChannel()
977 			? History::ClearType::Unload
978 			: History::ClearType::DeleteChat);
979 	}
980 	if (const auto channel = peer->asMegagroup()) {
981 		channel->addFlags(ChannelDataFlag::Left);
982 		if (const auto from = channel->getMigrateFromChat()) {
983 			if (const auto migrated = historyLoaded(from)) {
984 				migrated->updateChatListExistence();
985 			}
986 		}
987 	}
988 }
989 
cancelForwarding(not_null<History * > history)990 void Session::cancelForwarding(not_null<History*> history) {
991 	history->setForwardDraft({});
992 	session().changes().historyUpdated(
993 		history,
994 		Data::HistoryUpdate::Flag::ForwardDraft);
995 }
996 
chatsListLoaded(Data::Folder * folder)997 bool Session::chatsListLoaded(Data::Folder *folder) {
998 	return chatsList(folder)->loaded();
999 }
1000 
chatsListChanged(FolderId folderId)1001 void Session::chatsListChanged(FolderId folderId) {
1002 	chatsListChanged(folderId ? folder(folderId).get() : nullptr);
1003 }
1004 
chatsListChanged(Data::Folder * folder)1005 void Session::chatsListChanged(Data::Folder *folder) {
1006 	_chatsListChanged.fire_copy(folder);
1007 }
1008 
chatsListDone(Data::Folder * folder)1009 void Session::chatsListDone(Data::Folder *folder) {
1010 	if (folder) {
1011 		folder->chatsList()->setLoaded();
1012 	} else {
1013 		_chatsList.setLoaded();
1014 	}
1015 	_chatsListLoadedEvents.fire_copy(folder);
1016 }
1017 
userIsBotChanged(not_null<UserData * > user)1018 void Session::userIsBotChanged(not_null<UserData*> user) {
1019 	if (const auto history = this->history(user)) {
1020 		chatsFilters().refreshHistory(history);
1021 	}
1022 	_userIsBotChanges.fire_copy(user);
1023 }
1024 
userIsBotChanges() const1025 rpl::producer<not_null<UserData*>> Session::userIsBotChanges() const {
1026 	return _userIsBotChanges.events();
1027 }
1028 
botCommandsChanged(not_null<PeerData * > peer)1029 void Session::botCommandsChanged(not_null<PeerData*> peer) {
1030 	_botCommandsChanges.fire_copy(peer);
1031 }
1032 
botCommandsChanges() const1033 rpl::producer<not_null<PeerData*>> Session::botCommandsChanges() const {
1034 	return _botCommandsChanges.events();
1035 }
1036 
cache()1037 Storage::Cache::Database &Session::cache() {
1038 	return *_cache;
1039 }
1040 
cacheBigFile()1041 Storage::Cache::Database &Session::cacheBigFile() {
1042 	return *_bigFileCache;
1043 }
1044 
suggestStartExport(TimeId availableAt)1045 void Session::suggestStartExport(TimeId availableAt) {
1046 	_exportAvailableAt = availableAt;
1047 	suggestStartExport();
1048 }
1049 
clearExportSuggestion()1050 void Session::clearExportSuggestion() {
1051 	_exportAvailableAt = 0;
1052 	if (_exportSuggestion) {
1053 		_exportSuggestion->closeBox();
1054 	}
1055 }
1056 
suggestStartExport()1057 void Session::suggestStartExport() {
1058 	if (_exportAvailableAt <= 0) {
1059 		return;
1060 	}
1061 
1062 	const auto now = base::unixtime::now();
1063 	const auto left = (_exportAvailableAt <= now)
1064 		? 0
1065 		: (_exportAvailableAt - now);
1066 	if (left) {
1067 		base::call_delayed(
1068 			std::min(left + 5, 3600) * crl::time(1000),
1069 			_session,
1070 			[=] { suggestStartExport(); });
1071 	} else if (Core::App().exportManager().inProgress()) {
1072 		Export::View::ClearSuggestStart(&session());
1073 	} else {
1074 		_exportSuggestion = Export::View::SuggestStart(&session());
1075 	}
1076 }
1077 
passportCredentials() const1078 const Passport::SavedCredentials *Session::passportCredentials() const {
1079 	return _passportCredentials ? &_passportCredentials->first : nullptr;
1080 }
1081 
rememberPassportCredentials(Passport::SavedCredentials data,crl::time rememberFor)1082 void Session::rememberPassportCredentials(
1083 		Passport::SavedCredentials data,
1084 		crl::time rememberFor) {
1085 	Expects(rememberFor > 0);
1086 
1087 	static auto generation = 0;
1088 	_passportCredentials = std::make_unique<CredentialsWithGeneration>(
1089 		std::move(data),
1090 		++generation);
1091 	base::call_delayed(rememberFor, _session, [=, check = generation] {
1092 		if (_passportCredentials && _passportCredentials->second == check) {
1093 			forgetPassportCredentials();
1094 		}
1095 	});
1096 }
1097 
forgetPassportCredentials()1098 void Session::forgetPassportCredentials() {
1099 	_passportCredentials = nullptr;
1100 }
1101 
nameSortKey(const QString & name) const1102 QString Session::nameSortKey(const QString &name) const {
1103 	return TextUtilities::RemoveAccents(name).toLower();
1104 }
1105 
setupMigrationViewer()1106 void Session::setupMigrationViewer() {
1107 	session().changes().peerUpdates(
1108 		PeerUpdate::Flag::Migration
1109 	) | rpl::map([](const PeerUpdate &update) {
1110 		return update.peer->asChat();
1111 	}) | rpl::filter([=](ChatData *chat) {
1112 		return (chat != nullptr);
1113 	}) | rpl::start_with_next([=](not_null<ChatData*> chat) {
1114 		const auto channel = chat->migrateTo();
1115 		if (!channel) {
1116 			return;
1117 		}
1118 
1119 		chat->clearGroupCall();
1120 		if (const auto from = historyLoaded(chat)) {
1121 			if (const auto to = historyLoaded(channel)) {
1122 				if (to->inChatList() && from->inChatList()) {
1123 					removeChatListEntry(from);
1124 				}
1125 			}
1126 		}
1127 	}, _lifetime);
1128 }
1129 
setupChannelLeavingViewer()1130 void Session::setupChannelLeavingViewer() {
1131 	session().changes().peerUpdates(
1132 		PeerUpdate::Flag::ChannelAmIn
1133 	) | rpl::map([](const PeerUpdate &update) {
1134 		return update.peer->asChannel();
1135 	}) | rpl::start_with_next([=](not_null<ChannelData*> channel) {
1136 		if (channel->amIn()) {
1137 			channel->clearInvitePeek();
1138 		} else {
1139 			if (const auto history = historyLoaded(channel->id)) {
1140 				history->removeJoinedMessage();
1141 				history->updateChatListExistence();
1142 				history->updateChatListSortPosition();
1143 			}
1144 		}
1145 	}, _lifetime);
1146 }
1147 
setupPeerNameViewer()1148 void Session::setupPeerNameViewer() {
1149 	session().changes().realtimeNameUpdates(
1150 	) | rpl::start_with_next([=](const NameUpdate &update) {
1151 		const auto peer = update.peer;
1152 		if (const auto history = historyLoaded(peer)) {
1153 			history->refreshChatListNameSortKey();
1154 		}
1155 		const auto &oldLetters = update.oldFirstLetters;
1156 		_contactsNoChatsList.peerNameChanged(peer, oldLetters);
1157 		_contactsList.peerNameChanged(peer, oldLetters);
1158 
1159 	}, _lifetime);
1160 }
1161 
setupUserIsContactViewer()1162 void Session::setupUserIsContactViewer() {
1163 	session().changes().peerUpdates(
1164 		PeerUpdate::Flag::IsContact
1165 	) | rpl::map([](const PeerUpdate &update) {
1166 		return update.peer->asUser();
1167 	}) | rpl::start_with_next([=](not_null<UserData*> user) {
1168 		const auto i = _contactViews.find(peerToUser(user->id));
1169 		if (i != _contactViews.end()) {
1170 			for (const auto &view : i->second) {
1171 				requestViewResize(view);
1172 			}
1173 		}
1174 		if (!user->isLoaded()) {
1175 			LOG(("API Error: "
1176 				"userIsContactChanged() called for a not loaded user!"));
1177 			return;
1178 		}
1179 		if (user->isContact()) {
1180 			const auto history = this->history(user->id);
1181 			_contactsList.addByName(history);
1182 			if (!history->inChatList()) {
1183 				_contactsNoChatsList.addByName(history);
1184 			}
1185 		} else if (const auto history = historyLoaded(user)) {
1186 			_contactsNoChatsList.del(history);
1187 			_contactsList.del(history);
1188 		}
1189 	}, _lifetime);
1190 }
1191 
1192 Session::~Session() = default;
1193 
1194 template <typename Method>
enumerateItemViews(not_null<const HistoryItem * > item,Method method)1195 void Session::enumerateItemViews(
1196 		not_null<const HistoryItem*> item,
1197 		Method method) {
1198 	if (const auto i = _views.find(item); i != _views.end()) {
1199 		for (const auto view : i->second) {
1200 			method(view);
1201 		}
1202 	}
1203 }
1204 
photoLoadSettingsChanged()1205 void Session::photoLoadSettingsChanged() {
1206 	for (const auto &[id, photo] : _photos) {
1207 		photo->automaticLoadSettingsChanged();
1208 	}
1209 }
1210 
documentLoadSettingsChanged()1211 void Session::documentLoadSettingsChanged() {
1212 	for (const auto &[id, document] : _documents) {
1213 		document->automaticLoadSettingsChanged();
1214 	}
1215 }
1216 
notifyPhotoLayoutChanged(not_null<const PhotoData * > photo)1217 void Session::notifyPhotoLayoutChanged(not_null<const PhotoData*> photo) {
1218 	if (const auto i = _photoItems.find(photo); i != end(_photoItems)) {
1219 		for (const auto &item : i->second) {
1220 			notifyItemLayoutChange(item);
1221 		}
1222 	}
1223 }
1224 
requestPhotoViewRepaint(not_null<const PhotoData * > photo)1225 void Session::requestPhotoViewRepaint(not_null<const PhotoData*> photo) {
1226 	const auto i = _photoItems.find(photo);
1227 	if (i != end(_photoItems)) {
1228 		for (const auto &item : i->second) {
1229 			requestItemRepaint(item);
1230 		}
1231 	}
1232 }
1233 
notifyDocumentLayoutChanged(not_null<const DocumentData * > document)1234 void Session::notifyDocumentLayoutChanged(
1235 		not_null<const DocumentData*> document) {
1236 	const auto i = _documentItems.find(document);
1237 	if (i != end(_documentItems)) {
1238 		for (const auto &item : i->second) {
1239 			notifyItemLayoutChange(item);
1240 		}
1241 	}
1242 	if (const auto items = InlineBots::Layout::documentItems()) {
1243 		if (const auto i = items->find(document); i != items->end()) {
1244 			for (const auto &item : i->second) {
1245 				item->layoutChanged();
1246 			}
1247 		}
1248 	}
1249 }
1250 
requestDocumentViewRepaint(not_null<const DocumentData * > document)1251 void Session::requestDocumentViewRepaint(
1252 		not_null<const DocumentData*> document) {
1253 	const auto i = _documentItems.find(document);
1254 	if (i != end(_documentItems)) {
1255 		for (const auto &item : i->second) {
1256 			requestItemRepaint(item);
1257 		}
1258 	}
1259 }
1260 
requestPollViewRepaint(not_null<const PollData * > poll)1261 void Session::requestPollViewRepaint(not_null<const PollData*> poll) {
1262 	if (const auto i = _pollViews.find(poll); i != _pollViews.end()) {
1263 		for (const auto &view : i->second) {
1264 			requestViewResize(view);
1265 		}
1266 	}
1267 }
1268 
documentLoadProgress(not_null<DocumentData * > document)1269 void Session::documentLoadProgress(not_null<DocumentData*> document) {
1270 	requestDocumentViewRepaint(document);
1271 	session().documentUpdated.notify(document, true);
1272 
1273 	if (document->isAudioFile()) {
1274 		::Media::Player::instance()->documentLoadProgress(document);
1275 	}
1276 }
1277 
documentLoadDone(not_null<DocumentData * > document)1278 void Session::documentLoadDone(not_null<DocumentData*> document) {
1279 	notifyDocumentLayoutChanged(document);
1280 }
1281 
documentLoadFail(not_null<DocumentData * > document,bool started)1282 void Session::documentLoadFail(
1283 		not_null<DocumentData*> document,
1284 		bool started) {
1285 	notifyDocumentLayoutChanged(document);
1286 }
1287 
photoLoadProgress(not_null<PhotoData * > photo)1288 void Session::photoLoadProgress(not_null<PhotoData*> photo) {
1289 	requestPhotoViewRepaint(photo);
1290 }
1291 
photoLoadDone(not_null<PhotoData * > photo)1292 void Session::photoLoadDone(not_null<PhotoData*> photo) {
1293 	notifyPhotoLayoutChanged(photo);
1294 }
1295 
photoLoadFail(not_null<PhotoData * > photo,bool started)1296 void Session::photoLoadFail(
1297 		not_null<PhotoData*> photo,
1298 		bool started) {
1299 	notifyPhotoLayoutChanged(photo);
1300 }
1301 
markMediaRead(not_null<const DocumentData * > document)1302 void Session::markMediaRead(not_null<const DocumentData*> document) {
1303 	const auto i = _documentItems.find(document);
1304 	if (i != end(_documentItems)) {
1305 		_session->api().markMediaRead({ begin(i->second), end(i->second) });
1306 	}
1307 }
1308 
notifyItemLayoutChange(not_null<const HistoryItem * > item)1309 void Session::notifyItemLayoutChange(not_null<const HistoryItem*> item) {
1310 	_itemLayoutChanges.fire_copy(item);
1311 	enumerateItemViews(item, [&](not_null<ViewElement*> view) {
1312 		notifyViewLayoutChange(view);
1313 	});
1314 }
1315 
itemLayoutChanged() const1316 rpl::producer<not_null<const HistoryItem*>> Session::itemLayoutChanged() const {
1317 	return _itemLayoutChanges.events();
1318 }
1319 
notifyViewLayoutChange(not_null<const ViewElement * > view)1320 void Session::notifyViewLayoutChange(not_null<const ViewElement*> view) {
1321 	_viewLayoutChanges.fire_copy(view);
1322 }
1323 
viewLayoutChanged() const1324 rpl::producer<not_null<const ViewElement*>> Session::viewLayoutChanged() const {
1325 	return _viewLayoutChanges.events();
1326 }
1327 
notifyNewItemAdded(not_null<HistoryItem * > item)1328 void Session::notifyNewItemAdded(not_null<HistoryItem*> item) {
1329 	_newItemAdded.fire_copy(item);
1330 }
1331 
newItemAdded() const1332 rpl::producer<not_null<HistoryItem*>> Session::newItemAdded() const {
1333 	return _newItemAdded.events();
1334 }
1335 
changeMessageId(ChannelId channel,MsgId wasId,MsgId nowId)1336 void Session::changeMessageId(ChannelId channel, MsgId wasId, MsgId nowId) {
1337 	const auto list = messagesListForInsert(channel);
1338 	auto i = list->find(wasId);
1339 	Assert(i != list->end());
1340 	auto owned = std::move(i->second);
1341 	list->erase(i);
1342 	const auto [j, ok] = list->emplace(nowId, std::move(owned));
1343 
1344 	Ensures(ok);
1345 }
1346 
notifyItemIdChange(IdChange event)1347 void Session::notifyItemIdChange(IdChange event) {
1348 	const auto item = event.item;
1349 	changeMessageId(item->history()->channelId(), event.oldId, item->id);
1350 
1351 	_itemIdChanges.fire_copy(event);
1352 
1353 	const auto refreshViewDataId = [](not_null<ViewElement*> view) {
1354 		view->refreshDataId();
1355 	};
1356 	enumerateItemViews(item, refreshViewDataId);
1357 	if (const auto group = groups().find(item)) {
1358 		const auto leader = group->items.front();
1359 		if (leader != item) {
1360 			enumerateItemViews(leader, refreshViewDataId);
1361 		}
1362 	}
1363 }
1364 
itemIdChanged() const1365 rpl::producer<Session::IdChange> Session::itemIdChanged() const {
1366 	return _itemIdChanges.events();
1367 }
1368 
requestItemRepaint(not_null<const HistoryItem * > item)1369 void Session::requestItemRepaint(not_null<const HistoryItem*> item) {
1370 	_itemRepaintRequest.fire_copy(item);
1371 	auto repaintGroupLeader = false;
1372 	auto repaintView = [&](not_null<const ViewElement*> view) {
1373 		if (view->isHiddenByGroup()) {
1374 			repaintGroupLeader = true;
1375 		} else {
1376 			requestViewRepaint(view);
1377 		}
1378 	};
1379 	enumerateItemViews(item, repaintView);
1380 	if (repaintGroupLeader) {
1381 		if (const auto group = groups().find(item)) {
1382 			const auto leader = group->items.front();
1383 			if (leader != item) {
1384 				enumerateItemViews(leader, repaintView);
1385 			}
1386 		}
1387 	}
1388 	const auto history = item->history();
1389 	if (history->lastItemDialogsView.dependsOn(item)) {
1390 		history->updateChatListEntry();
1391 	}
1392 }
1393 
itemRepaintRequest() const1394 rpl::producer<not_null<const HistoryItem*>> Session::itemRepaintRequest() const {
1395 	return _itemRepaintRequest.events();
1396 }
1397 
requestViewRepaint(not_null<const ViewElement * > view)1398 void Session::requestViewRepaint(not_null<const ViewElement*> view) {
1399 	_viewRepaintRequest.fire_copy(view);
1400 }
1401 
viewRepaintRequest() const1402 rpl::producer<not_null<const ViewElement*>> Session::viewRepaintRequest() const {
1403 	return _viewRepaintRequest.events();
1404 }
1405 
requestItemResize(not_null<const HistoryItem * > item)1406 void Session::requestItemResize(not_null<const HistoryItem*> item) {
1407 	_itemResizeRequest.fire_copy(item);
1408 	enumerateItemViews(item, [&](not_null<ViewElement*> view) {
1409 		requestViewResize(view);
1410 	});
1411 }
1412 
itemResizeRequest() const1413 rpl::producer<not_null<const HistoryItem*>> Session::itemResizeRequest() const {
1414 	return _itemResizeRequest.events();
1415 }
1416 
requestViewResize(not_null<ViewElement * > view)1417 void Session::requestViewResize(not_null<ViewElement*> view) {
1418 	view->setPendingResize();
1419 	_viewResizeRequest.fire_copy(view);
1420 	notifyViewLayoutChange(view);
1421 }
1422 
viewResizeRequest() const1423 rpl::producer<not_null<ViewElement*>> Session::viewResizeRequest() const {
1424 	return _viewResizeRequest.events();
1425 }
1426 
requestItemViewRefresh(not_null<HistoryItem * > item)1427 void Session::requestItemViewRefresh(not_null<HistoryItem*> item) {
1428 	if (const auto view = item->mainView()) {
1429 		notifyHistoryChangeDelayed(item->history());
1430 		view->refreshInBlock();
1431 	}
1432 	_itemViewRefreshRequest.fire_copy(item);
1433 }
1434 
itemViewRefreshRequest() const1435 rpl::producer<not_null<HistoryItem*>> Session::itemViewRefreshRequest() const {
1436 	return _itemViewRefreshRequest.events();
1437 }
1438 
requestItemTextRefresh(not_null<HistoryItem * > item)1439 void Session::requestItemTextRefresh(not_null<HistoryItem*> item) {
1440 	if (const auto i = _views.find(item); i != _views.end()) {
1441 		for (const auto view : i->second) {
1442 			if (const auto media = view->media()) {
1443 				media->parentTextUpdated();
1444 			}
1445 		}
1446 	}
1447 }
1448 
requestAnimationPlayInline(not_null<HistoryItem * > item)1449 void Session::requestAnimationPlayInline(not_null<HistoryItem*> item) {
1450 	_animationPlayInlineRequest.fire_copy(item);
1451 
1452 	if (const auto media = item->media()) {
1453 		if (const auto data = media->document()) {
1454 			if (data && data->isVideoMessage()) {
1455 				const auto msgId = item->fullId();
1456 				::Media::Player::instance()->playPause({ data, msgId });
1457 			}
1458 		}
1459 	}
1460 }
1461 
animationPlayInlineRequest() const1462 rpl::producer<not_null<HistoryItem*>> Session::animationPlayInlineRequest() const {
1463 	return _animationPlayInlineRequest.events();
1464 }
1465 
itemRemoved() const1466 rpl::producer<not_null<const HistoryItem*>> Session::itemRemoved() const {
1467 	return _itemRemoved.events();
1468 }
1469 
itemRemoved(FullMsgId itemId) const1470 rpl::producer<not_null<const HistoryItem*>> Session::itemRemoved(
1471 		FullMsgId itemId) const {
1472 	return itemRemoved(
1473 	) | rpl::filter([=](not_null<const HistoryItem*> item) {
1474 		return (itemId == item->fullId());
1475 	});
1476 }
1477 
notifyViewRemoved(not_null<const ViewElement * > view)1478 void Session::notifyViewRemoved(not_null<const ViewElement*> view) {
1479 	_viewRemoved.fire_copy(view);
1480 }
1481 
viewRemoved() const1482 rpl::producer<not_null<const ViewElement*>> Session::viewRemoved() const {
1483 	return _viewRemoved.events();
1484 }
1485 
notifyHistoryUnloaded(not_null<const History * > history)1486 void Session::notifyHistoryUnloaded(not_null<const History*> history) {
1487 	_historyUnloaded.fire_copy(history);
1488 }
1489 
historyUnloaded() const1490 rpl::producer<not_null<const History*>> Session::historyUnloaded() const {
1491 	return _historyUnloaded.events();
1492 }
1493 
notifyHistoryCleared(not_null<const History * > history)1494 void Session::notifyHistoryCleared(not_null<const History*> history) {
1495 	_historyCleared.fire_copy(history);
1496 }
1497 
historyCleared() const1498 rpl::producer<not_null<const History*>> Session::historyCleared() const {
1499 	return _historyCleared.events();
1500 }
1501 
notifyHistoryChangeDelayed(not_null<History * > history)1502 void Session::notifyHistoryChangeDelayed(not_null<History*> history) {
1503 	history->setHasPendingResizedItems();
1504 	_historiesChanged.insert(history);
1505 }
1506 
historyChanged() const1507 rpl::producer<not_null<History*>> Session::historyChanged() const {
1508 	return _historyChanged.events();
1509 }
1510 
sendHistoryChangeNotifications()1511 void Session::sendHistoryChangeNotifications() {
1512 	for (const auto &history : base::take(_historiesChanged)) {
1513 		_historyChanged.fire_copy(history);
1514 	}
1515 }
1516 
notifyPinnedDialogsOrderUpdated()1517 void Session::notifyPinnedDialogsOrderUpdated() {
1518 	_pinnedDialogsOrderUpdated.fire({});
1519 }
1520 
pinnedDialogsOrderUpdated() const1521 rpl::producer<> Session::pinnedDialogsOrderUpdated() const {
1522 	return _pinnedDialogsOrderUpdated.events();
1523 }
1524 
registerHeavyViewPart(not_null<ViewElement * > view)1525 void Session::registerHeavyViewPart(not_null<ViewElement*> view) {
1526 	_heavyViewParts.emplace(view);
1527 }
1528 
unregisterHeavyViewPart(not_null<ViewElement * > view)1529 void Session::unregisterHeavyViewPart(not_null<ViewElement*> view) {
1530 	_heavyViewParts.remove(view);
1531 }
1532 
unloadHeavyViewParts(not_null<HistoryView::ElementDelegate * > delegate)1533 void Session::unloadHeavyViewParts(
1534 		not_null<HistoryView::ElementDelegate*> delegate) {
1535 	if (_heavyViewParts.empty()) {
1536 		return;
1537 	}
1538 	const auto remove = ranges::count(
1539 		_heavyViewParts,
1540 		delegate,
1541 		[](not_null<ViewElement*> element) { return element->delegate(); });
1542 	if (remove == _heavyViewParts.size()) {
1543 		for (const auto &view : base::take(_heavyViewParts)) {
1544 			view->unloadHeavyPart();
1545 		}
1546 	} else {
1547 		auto remove = std::vector<not_null<ViewElement*>>();
1548 		for (const auto &view : _heavyViewParts) {
1549 			if (view->delegate() == delegate) {
1550 				remove.push_back(view);
1551 			}
1552 		}
1553 		for (const auto view : remove) {
1554 			view->unloadHeavyPart();
1555 		}
1556 	}
1557 }
1558 
unloadHeavyViewParts(not_null<HistoryView::ElementDelegate * > delegate,int from,int till)1559 void Session::unloadHeavyViewParts(
1560 		not_null<HistoryView::ElementDelegate*> delegate,
1561 		int from,
1562 		int till) {
1563 	if (_heavyViewParts.empty()) {
1564 		return;
1565 	}
1566 	auto remove = std::vector<not_null<ViewElement*>>();
1567 	for (const auto &view : _heavyViewParts) {
1568 		if (view->delegate() == delegate
1569 			&& !delegate->elementIntersectsRange(view, from, till)) {
1570 			remove.push_back(view);
1571 		}
1572 	}
1573 	for (const auto view : remove) {
1574 		view->unloadHeavyPart();
1575 	}
1576 }
1577 
removeMegagroupParticipant(not_null<ChannelData * > channel,not_null<UserData * > user)1578 void Session::removeMegagroupParticipant(
1579 		not_null<ChannelData*> channel,
1580 		not_null<UserData*> user) {
1581 	_megagroupParticipantRemoved.fire({ channel, user });
1582 }
1583 
megagroupParticipantRemoved() const1584 auto Session::megagroupParticipantRemoved() const
1585 -> rpl::producer<MegagroupParticipant> {
1586 	return _megagroupParticipantRemoved.events();
1587 }
1588 
megagroupParticipantRemoved(not_null<ChannelData * > channel) const1589 rpl::producer<not_null<UserData*>> Session::megagroupParticipantRemoved(
1590 		not_null<ChannelData*> channel) const {
1591 	return megagroupParticipantRemoved(
1592 	) | rpl::filter([channel](auto updateChannel, auto user) {
1593 		return (updateChannel == channel);
1594 	}) | rpl::map([](auto updateChannel, auto user) {
1595 		return user;
1596 	});
1597 }
1598 
addNewMegagroupParticipant(not_null<ChannelData * > channel,not_null<UserData * > user)1599 void Session::addNewMegagroupParticipant(
1600 		not_null<ChannelData*> channel,
1601 		not_null<UserData*> user) {
1602 	_megagroupParticipantAdded.fire({ channel, user });
1603 }
1604 
megagroupParticipantAdded() const1605 auto Session::megagroupParticipantAdded() const
1606 -> rpl::producer<MegagroupParticipant> {
1607 	return _megagroupParticipantAdded.events();
1608 }
1609 
megagroupParticipantAdded(not_null<ChannelData * > channel) const1610 rpl::producer<not_null<UserData*>> Session::megagroupParticipantAdded(
1611 		not_null<ChannelData*> channel) const {
1612 	return megagroupParticipantAdded(
1613 	) | rpl::filter([channel](auto updateChannel, auto user) {
1614 		return (updateChannel == channel);
1615 	}) | rpl::map([](auto updateChannel, auto user) {
1616 		return user;
1617 	});
1618 }
1619 
idsToItems(const MessageIdsList & ids) const1620 HistoryItemsList Session::idsToItems(
1621 		const MessageIdsList &ids) const {
1622 	return ranges::views::all(
1623 		ids
1624 	) | ranges::views::transform([&](const FullMsgId &fullId) {
1625 		return message(fullId);
1626 	}) | ranges::views::filter([](HistoryItem *item) {
1627 		return item != nullptr;
1628 	}) | ranges::views::transform([](HistoryItem *item) {
1629 		return not_null<HistoryItem*>(item);
1630 	}) | ranges::to_vector;
1631 }
1632 
itemsToIds(const HistoryItemsList & items) const1633 MessageIdsList Session::itemsToIds(
1634 		const HistoryItemsList &items) const {
1635 	return ranges::views::all(
1636 		items
1637 	) | ranges::views::transform([](not_null<HistoryItem*> item) {
1638 		return item->fullId();
1639 	}) | ranges::to_vector;
1640 }
1641 
itemOrItsGroup(not_null<HistoryItem * > item) const1642 MessageIdsList Session::itemOrItsGroup(not_null<HistoryItem*> item) const {
1643 	if (const auto group = groups().find(item)) {
1644 		return itemsToIds(group->items);
1645 	}
1646 	return { 1, item->fullId() };
1647 }
1648 
setChatPinned(const Dialogs::Key & key,FilterId filterId,bool pinned)1649 void Session::setChatPinned(
1650 		const Dialogs::Key &key,
1651 		FilterId filterId,
1652 		bool pinned) {
1653 	Expects(key.entry()->folderKnown());
1654 
1655 	const auto list = filterId
1656 		? chatsFilters().chatsList(filterId)
1657 		: chatsList(key.entry()->folder());
1658 	list->pinned()->setPinned(key, pinned);
1659 	notifyPinnedDialogsOrderUpdated();
1660 }
1661 
setPinnedFromDialog(const Dialogs::Key & key,bool pinned)1662 void Session::setPinnedFromDialog(const Dialogs::Key &key, bool pinned) {
1663 	Expects(key.entry()->folderKnown());
1664 
1665 	const auto list = chatsList(key.entry()->folder())->pinned();
1666 	if (pinned) {
1667 		list->addPinned(key);
1668 	} else {
1669 		list->setPinned(key, false);
1670 	}
1671 }
1672 
applyPinnedChats(Data::Folder * folder,const QVector<MTPDialogPeer> & list)1673 void Session::applyPinnedChats(
1674 		Data::Folder *folder,
1675 		const QVector<MTPDialogPeer> &list) {
1676 	for (const auto &peer : list) {
1677 		peer.match([&](const MTPDdialogPeer &data) {
1678 			const auto history = this->history(peerFromMTP(data.vpeer()));
1679 			if (folder) {
1680 				history->setFolder(folder);
1681 			} else {
1682 				history->clearFolder();
1683 			}
1684 		}, [&](const MTPDdialogPeerFolder &data) {
1685 			if (folder) {
1686 				LOG(("API Error: Nested folders detected."));
1687 			}
1688 		});
1689 	}
1690 	chatsList(folder)->pinned()->applyList(this, list);
1691 	notifyPinnedDialogsOrderUpdated();
1692 }
1693 
applyDialogs(Data::Folder * requestFolder,const QVector<MTPMessage> & messages,const QVector<MTPDialog> & dialogs,std::optional<int> count)1694 void Session::applyDialogs(
1695 		Data::Folder *requestFolder,
1696 		const QVector<MTPMessage> &messages,
1697 		const QVector<MTPDialog> &dialogs,
1698 		std::optional<int> count) {
1699 	processMessages(messages, NewMessageType::Last);
1700 	for (const auto &dialog : dialogs) {
1701 		dialog.match([&](const auto &data) {
1702 			applyDialog(requestFolder, data);
1703 		});
1704 	}
1705 	if (requestFolder && count) {
1706 		requestFolder->chatsList()->setCloudListSize(*count);
1707 	}
1708 }
1709 
applyDialog(Data::Folder * requestFolder,const MTPDdialog & data)1710 void Session::applyDialog(
1711 		Data::Folder *requestFolder,
1712 		const MTPDdialog &data) {
1713 	const auto peerId = peerFromMTP(data.vpeer());
1714 	if (!peerId) {
1715 		return;
1716 	}
1717 
1718 	const auto history = this->history(peerId);
1719 	history->applyDialog(requestFolder, data);
1720 	setPinnedFromDialog(history, data.is_pinned());
1721 
1722 	if (const auto from = history->peer->migrateFrom()) {
1723 		if (const auto historyFrom = historyLoaded(from)) {
1724 			removeChatListEntry(historyFrom);
1725 		}
1726 	} else if (const auto to = history->peer->migrateTo()) {
1727 		if (to->amIn()) {
1728 			removeChatListEntry(history);
1729 		}
1730 	}
1731 }
1732 
applyDialog(Data::Folder * requestFolder,const MTPDdialogFolder & data)1733 void Session::applyDialog(
1734 		Data::Folder *requestFolder,
1735 		const MTPDdialogFolder &data) {
1736 	if (requestFolder) {
1737 		LOG(("API Error: requestFolder != nullptr for dialogFolder."));
1738 	}
1739 	const auto folder = processFolder(data.vfolder());
1740 	folder->applyDialog(data);
1741 	setPinnedFromDialog(folder, data.is_pinned());
1742 }
1743 
pinnedChatsCount(Data::Folder * folder,FilterId filterId) const1744 int Session::pinnedChatsCount(
1745 		Data::Folder *folder,
1746 		FilterId filterId) const {
1747 	if (!filterId) {
1748 		return pinnedChatsOrder(folder, filterId).size();
1749 	}
1750 	const auto &list = chatsFilters().list();
1751 	const auto i = ranges::find(list, filterId, &Data::ChatFilter::id);
1752 	return (i != end(list)) ? i->pinned().size() : 0;
1753 }
1754 
pinnedChatsLimit(Data::Folder * folder,FilterId filterId) const1755 int Session::pinnedChatsLimit(
1756 		Data::Folder *folder,
1757 		FilterId filterId) const {
1758 	if (!filterId) {
1759 		return folder
1760 			? session().serverConfig().pinnedDialogsInFolderMax.current()
1761 			: session().serverConfig().pinnedDialogsCountMax.current();
1762 	}
1763 	const auto &list = chatsFilters().list();
1764 	const auto i = ranges::find(list, filterId, &Data::ChatFilter::id);
1765 	const auto pinned = (i != end(list)) ? i->pinned().size() : 0;
1766 	const auto already = (i != end(list)) ? i->always().size() : 0;
1767 	return Data::ChatFilter::kPinnedLimit + pinned - already;
1768 }
1769 
pinnedChatsOrder(Data::Folder * folder,FilterId filterId) const1770 const std::vector<Dialogs::Key> &Session::pinnedChatsOrder(
1771 		Data::Folder *folder,
1772 		FilterId filterId) const {
1773 	const auto list = filterId
1774 		? chatsFilters().chatsList(filterId)
1775 		: chatsList(folder);
1776 	return list->pinned()->order();
1777 }
1778 
clearPinnedChats(Data::Folder * folder)1779 void Session::clearPinnedChats(Data::Folder *folder) {
1780 	chatsList(folder)->pinned()->clear();
1781 }
1782 
reorderTwoPinnedChats(FilterId filterId,const Dialogs::Key & key1,const Dialogs::Key & key2)1783 void Session::reorderTwoPinnedChats(
1784 		FilterId filterId,
1785 		const Dialogs::Key &key1,
1786 		const Dialogs::Key &key2) {
1787 	Expects(key1.entry()->folderKnown() && key2.entry()->folderKnown());
1788 	Expects(filterId || (key1.entry()->folder() == key2.entry()->folder()));
1789 
1790 	const auto list = filterId
1791 		? chatsFilters().chatsList(filterId)
1792 		: chatsList(key1.entry()->folder());
1793 	list->pinned()->reorder(key1, key2);
1794 	notifyPinnedDialogsOrderUpdated();
1795 }
1796 
checkEntitiesAndViewsUpdate(const MTPDmessage & data)1797 bool Session::checkEntitiesAndViewsUpdate(const MTPDmessage &data) {
1798 	const auto peer = peerFromMTP(data.vpeer_id());
1799 	const auto existing = message(peerToChannel(peer), data.vid().v);
1800 	if (!existing) {
1801 		return false;
1802 	}
1803 	existing->applySentMessage(data);
1804 	const auto result = (existing->mainView() != nullptr);
1805 	if (result) {
1806 		stickers().checkSavedGif(existing);
1807 	}
1808 	session().changes().messageUpdated(
1809 		existing,
1810 		Data::MessageUpdate::Flag::NewMaybeAdded);
1811 	return result;
1812 }
1813 
updateEditedMessage(const MTPMessage & data)1814 void Session::updateEditedMessage(const MTPMessage &data) {
1815 	const auto existing = data.match([](const MTPDmessageEmpty &)
1816 			-> HistoryItem* {
1817 		return nullptr;
1818 	}, [&](const auto &data) {
1819 		const auto peer = peerFromMTP(data.vpeer_id());
1820 		return message(peerToChannel(peer), data.vid().v);
1821 	});
1822 	if (!existing) {
1823 		return;
1824 	}
1825 	if (existing->isLocalUpdateMedia() && data.type() == mtpc_message) {
1826 		checkEntitiesAndViewsUpdate(data.c_message());
1827 	}
1828 	data.match([](const MTPDmessageEmpty &) {
1829 	}, [&](const MTPDmessageService &data) {
1830 		existing->applyEdition(data);
1831 	}, [&](const auto &data) {
1832 		existing->applyEdition(HistoryMessageEdition(_session, data));
1833 	});
1834 }
1835 
processMessages(const QVector<MTPMessage> & data,NewMessageType type)1836 void Session::processMessages(
1837 		const QVector<MTPMessage> &data,
1838 		NewMessageType type) {
1839 	auto indices = base::flat_map<uint64, int>();
1840 	for (int i = 0, l = data.size(); i != l; ++i) {
1841 		const auto &message = data[i];
1842 		if (message.type() == mtpc_message) {
1843 			const auto &data = message.c_message();
1844 			// new message, index my forwarded messages to links overview
1845 			if ((type == NewMessageType::Unread)
1846 				&& checkEntitiesAndViewsUpdate(data)) {
1847 				continue;
1848 			}
1849 		}
1850 		const auto id = IdFromMessage(message); // Only 32 bit values here.
1851 		indices.emplace((uint64(uint32(id.bare)) << 32) | uint64(i), i);
1852 	}
1853 	for (const auto &[position, index] : indices) {
1854 		addNewMessage(
1855 			data[index],
1856 			MessageFlags(),
1857 			type);
1858 	}
1859 }
1860 
processMessages(const MTPVector<MTPMessage> & data,NewMessageType type)1861 void Session::processMessages(
1862 		const MTPVector<MTPMessage> &data,
1863 		NewMessageType type) {
1864 	processMessages(data.v, type);
1865 }
1866 
processExistingMessages(ChannelData * channel,const MTPmessages_Messages & data)1867 void Session::processExistingMessages(
1868 		ChannelData *channel,
1869 		const MTPmessages_Messages &data) {
1870 	data.match([&](const MTPDmessages_channelMessages &data) {
1871 		if (channel) {
1872 			channel->ptsReceived(data.vpts().v);
1873 		} else {
1874 			LOG(("App Error: received messages.channelMessages!"));
1875 		}
1876 	}, [](const auto &) {});
1877 
1878 	data.match([&](const MTPDmessages_messagesNotModified&) {
1879 		LOG(("API Error: received messages.messagesNotModified!"));
1880 	}, [&](const auto &data) {
1881 		processUsers(data.vusers());
1882 		processChats(data.vchats());
1883 		processMessages(data.vmessages(), NewMessageType::Existing);
1884 	});
1885 }
1886 
messagesList(ChannelId channelId) const1887 const Session::Messages *Session::messagesList(ChannelId channelId) const {
1888 	if (channelId == NoChannel) {
1889 		return &_messages;
1890 	}
1891 	const auto i = _channelMessages.find(channelId);
1892 	return (i != end(_channelMessages)) ? &i->second : nullptr;
1893 }
1894 
messagesListForInsert(ChannelId channelId)1895 auto Session::messagesListForInsert(ChannelId channelId)
1896 -> not_null<Messages*> {
1897 	return (channelId == NoChannel)
1898 		? &_messages
1899 		: &_channelMessages[channelId];
1900 }
1901 
registerMessage(not_null<HistoryItem * > item)1902 void Session::registerMessage(not_null<HistoryItem*> item) {
1903 	const auto list = messagesListForInsert(item->channelId());
1904 	const auto itemId = item->id;
1905 	const auto i = list->find(itemId);
1906 	if (i != list->end()) {
1907 		LOG(("App Error: Trying to re-registerMessage()."));
1908 		i->second->destroy();
1909 	}
1910 	list->emplace(itemId, item);
1911 }
1912 
registerMessageTTL(TimeId when,not_null<HistoryItem * > item)1913 void Session::registerMessageTTL(TimeId when, not_null<HistoryItem*> item) {
1914 	Expects(when > 0);
1915 
1916 	auto &list = _ttlMessages[when];
1917 	list.emplace(item);
1918 
1919 	const auto nearest = _ttlMessages.begin()->first;
1920 	if (nearest < when && _ttlCheckTimer.isActive()) {
1921 		return;
1922 	}
1923 	scheduleNextTTLs();
1924 }
1925 
scheduleNextTTLs()1926 void Session::scheduleNextTTLs() {
1927 	if (_ttlMessages.empty()) {
1928 		return;
1929 	}
1930 	const auto nearest = _ttlMessages.begin()->first;
1931 	const auto now = base::unixtime::now();
1932 
1933 	// Set timer not more than for 24 hours.
1934 	const auto maxTimeout = TimeId(86400);
1935 	const auto timeout = std::min(std::max(now, nearest) - now, maxTimeout);
1936 	_ttlCheckTimer.callOnce(timeout * crl::time(1000));
1937 }
1938 
unregisterMessageTTL(TimeId when,not_null<HistoryItem * > item)1939 void Session::unregisterMessageTTL(
1940 		TimeId when,
1941 		not_null<HistoryItem*> item) {
1942 	Expects(when > 0);
1943 
1944 	const auto i = _ttlMessages.find(when);
1945 	if (i == end(_ttlMessages)) {
1946 		return;
1947 	}
1948 	auto &list = i->second;
1949 	list.erase(item);
1950 	if (list.empty()) {
1951 		_ttlMessages.erase(i);
1952 	}
1953 }
1954 
checkTTLs()1955 void Session::checkTTLs() {
1956 	_ttlCheckTimer.cancel();
1957 	const auto now = base::unixtime::now();
1958 	while (!_ttlMessages.empty() && _ttlMessages.begin()->first <= now) {
1959 		_ttlMessages.begin()->second.front()->destroy();
1960 	}
1961 	scheduleNextTTLs();
1962 }
1963 
processMessagesDeleted(ChannelId channelId,const QVector<MTPint> & data)1964 void Session::processMessagesDeleted(
1965 		ChannelId channelId,
1966 		const QVector<MTPint> &data) {
1967 	const auto list = messagesList(channelId);
1968 	const auto affected = (channelId != NoChannel)
1969 		? historyLoaded(peerFromChannel(channelId))
1970 		: nullptr;
1971 	if (!list && !affected) {
1972 		return;
1973 	}
1974 
1975 	auto historiesToCheck = base::flat_set<not_null<History*>>();
1976 	for (const auto &messageId : data) {
1977 		const auto i = list ? list->find(messageId.v) : Messages::iterator();
1978 		if (list && i != list->end()) {
1979 			const auto history = i->second->history();
1980 			i->second->destroy();
1981 			if (!history->chatListMessageKnown()) {
1982 				historiesToCheck.emplace(history);
1983 			}
1984 		} else if (affected) {
1985 			affected->unknownMessageDeleted(messageId.v);
1986 		}
1987 	}
1988 	for (const auto &history : historiesToCheck) {
1989 		history->requestChatListMessage();
1990 	}
1991 }
1992 
removeDependencyMessage(not_null<HistoryItem * > item)1993 void Session::removeDependencyMessage(not_null<HistoryItem*> item) {
1994 	const auto i = _dependentMessages.find(item);
1995 	if (i == end(_dependentMessages)) {
1996 		return;
1997 	}
1998 	const auto items = std::move(i->second);
1999 	_dependentMessages.erase(i);
2000 
2001 	for (const auto &dependent : items) {
2002 		dependent->dependencyItemRemoved(item);
2003 	}
2004 }
2005 
unregisterMessage(not_null<HistoryItem * > item)2006 void Session::unregisterMessage(not_null<HistoryItem*> item) {
2007 	const auto peerId = item->history()->peer->id;
2008 	_itemRemoved.fire_copy(item);
2009 	session().changes().messageUpdated(
2010 		item,
2011 		Data::MessageUpdate::Flag::Destroyed);
2012 	groups().unregisterMessage(item);
2013 	removeDependencyMessage(item);
2014 	messagesListForInsert(peerToChannel(peerId))->erase(item->id);
2015 }
2016 
nextLocalMessageId()2017 MsgId Session::nextLocalMessageId() {
2018 	Expects(_localMessageIdCounter < EndClientMsgId);
2019 
2020 	return _localMessageIdCounter++;
2021 }
2022 
setSuggestToGigagroup(not_null<ChannelData * > group,bool suggest)2023 void Session::setSuggestToGigagroup(
2024 		not_null<ChannelData*> group,
2025 		bool suggest) {
2026 	if (suggest) {
2027 		_suggestToGigagroup.emplace(group);
2028 	} else {
2029 		_suggestToGigagroup.remove(group);
2030 	}
2031 }
2032 
suggestToGigagroup(not_null<ChannelData * > group) const2033 bool Session::suggestToGigagroup(not_null<ChannelData*> group) const {
2034 	return _suggestToGigagroup.contains(group);
2035 }
2036 
message(ChannelId channelId,MsgId itemId) const2037 HistoryItem *Session::message(ChannelId channelId, MsgId itemId) const {
2038 	if (!itemId) {
2039 		return nullptr;
2040 	}
2041 
2042 	const auto data = messagesList(channelId);
2043 	if (!data) {
2044 		return nullptr;
2045 	}
2046 
2047 	const auto i = data->find(itemId);
2048 	return (i != data->end()) ? i->second.get() : nullptr;
2049 }
2050 
message(const ChannelData * channel,MsgId itemId) const2051 HistoryItem *Session::message(
2052 		const ChannelData *channel,
2053 		MsgId itemId) const {
2054 	return message(channel ? peerToChannel(channel->id) : 0, itemId);
2055 }
2056 
message(FullMsgId itemId) const2057 HistoryItem *Session::message(FullMsgId itemId) const {
2058 	return message(itemId.channel, itemId.msg);
2059 }
2060 
updateDependentMessages(not_null<HistoryItem * > item)2061 void Session::updateDependentMessages(not_null<HistoryItem*> item) {
2062 	const auto i = _dependentMessages.find(item);
2063 	if (i != end(_dependentMessages)) {
2064 		for (const auto &dependent : i->second) {
2065 			dependent->updateDependencyItem();
2066 		}
2067 	}
2068 	session().changes().messageUpdated(
2069 		item,
2070 		Data::MessageUpdate::Flag::Edited);
2071 }
2072 
registerDependentMessage(not_null<HistoryItem * > dependent,not_null<HistoryItem * > dependency)2073 void Session::registerDependentMessage(
2074 		not_null<HistoryItem*> dependent,
2075 		not_null<HistoryItem*> dependency) {
2076 	_dependentMessages[dependency].emplace(dependent);
2077 }
2078 
unregisterDependentMessage(not_null<HistoryItem * > dependent,not_null<HistoryItem * > dependency)2079 void Session::unregisterDependentMessage(
2080 		not_null<HistoryItem*> dependent,
2081 		not_null<HistoryItem*> dependency) {
2082 	const auto i = _dependentMessages.find(dependency);
2083 	if (i != end(_dependentMessages)) {
2084 		if (i->second.remove(dependent) && i->second.empty()) {
2085 			_dependentMessages.erase(i);
2086 		}
2087 	}
2088 }
2089 
registerMessageRandomId(uint64 randomId,FullMsgId itemId)2090 void Session::registerMessageRandomId(uint64 randomId, FullMsgId itemId) {
2091 	_messageByRandomId.emplace(randomId, itemId);
2092 }
2093 
unregisterMessageRandomId(uint64 randomId)2094 void Session::unregisterMessageRandomId(uint64 randomId) {
2095 	_messageByRandomId.remove(randomId);
2096 }
2097 
messageIdByRandomId(uint64 randomId) const2098 FullMsgId Session::messageIdByRandomId(uint64 randomId) const {
2099 	const auto i = _messageByRandomId.find(randomId);
2100 	return (i != end(_messageByRandomId)) ? i->second : FullMsgId();
2101 }
2102 
registerMessageSentData(uint64 randomId,PeerId peerId,const QString & text)2103 void Session::registerMessageSentData(
2104 		uint64 randomId,
2105 		PeerId peerId,
2106 		const QString &text) {
2107 	_sentMessagesData.emplace(randomId, SentData{ peerId, text });
2108 }
2109 
unregisterMessageSentData(uint64 randomId)2110 void Session::unregisterMessageSentData(uint64 randomId) {
2111 	_sentMessagesData.remove(randomId);
2112 }
2113 
messageSentData(uint64 randomId) const2114 Session::SentData Session::messageSentData(uint64 randomId) const {
2115 	const auto i = _sentMessagesData.find(randomId);
2116 	return (i != end(_sentMessagesData)) ? i->second : SentData();
2117 }
2118 
defaultNotifySettings(not_null<const PeerData * > peer)2119 NotifySettings &Session::defaultNotifySettings(
2120 		not_null<const PeerData*> peer) {
2121 	return peer->isUser()
2122 		? _defaultUserNotifySettings
2123 		: (peer->isChat() || peer->isMegagroup())
2124 		? _defaultChatNotifySettings
2125 		: _defaultBroadcastNotifySettings;
2126 }
2127 
defaultNotifySettings(not_null<const PeerData * > peer) const2128 const NotifySettings &Session::defaultNotifySettings(
2129 		not_null<const PeerData*> peer) const {
2130 	return peer->isUser()
2131 		? _defaultUserNotifySettings
2132 		: (peer->isChat() || peer->isMegagroup())
2133 		? _defaultChatNotifySettings
2134 		: _defaultBroadcastNotifySettings;
2135 }
2136 
updateNotifySettingsLocal(not_null<PeerData * > peer)2137 void Session::updateNotifySettingsLocal(not_null<PeerData*> peer) {
2138 	const auto history = historyLoaded(peer->id);
2139 	auto changesIn = crl::time(0);
2140 	const auto muted = notifyIsMuted(peer, &changesIn);
2141 	if (history && history->changeMute(muted)) {
2142 		// Notification already sent.
2143 	} else {
2144 		session().changes().peerUpdated(
2145 			peer,
2146 			PeerUpdate::Flag::Notifications);
2147 	}
2148 
2149 	if (muted) {
2150 		_mutedPeers.emplace(peer);
2151 		unmuteByFinishedDelayed(changesIn);
2152 		if (history) {
2153 			Core::App().notifications().clearIncomingFromHistory(history);
2154 		}
2155 	} else {
2156 		_mutedPeers.erase(peer);
2157 	}
2158 }
2159 
unmuteByFinishedDelayed(crl::time delay)2160 void Session::unmuteByFinishedDelayed(crl::time delay) {
2161 	accumulate_min(delay, kMaxNotifyCheckDelay);
2162 	if (!_unmuteByFinishedTimer.isActive()
2163 		|| _unmuteByFinishedTimer.remainingTime() > delay) {
2164 		_unmuteByFinishedTimer.callOnce(delay);
2165 	}
2166 }
2167 
unmuteByFinished()2168 void Session::unmuteByFinished() {
2169 	auto changesInMin = crl::time(0);
2170 	for (auto i = begin(_mutedPeers); i != end(_mutedPeers);) {
2171 		const auto history = historyLoaded((*i)->id);
2172 		auto changesIn = crl::time(0);
2173 		const auto muted = notifyIsMuted(*i, &changesIn);
2174 		if (muted) {
2175 			if (history) {
2176 				history->changeMute(true);
2177 			}
2178 			if (!changesInMin || changesInMin > changesIn) {
2179 				changesInMin = changesIn;
2180 			}
2181 			++i;
2182 		} else {
2183 			if (history) {
2184 				history->changeMute(false);
2185 			}
2186 			i = _mutedPeers.erase(i);
2187 		}
2188 	}
2189 	if (changesInMin) {
2190 		unmuteByFinishedDelayed(changesInMin);
2191 	}
2192 }
2193 
addNewMessage(const MTPMessage & data,MessageFlags localFlags,NewMessageType type)2194 HistoryItem *Session::addNewMessage(
2195 		const MTPMessage &data,
2196 		MessageFlags localFlags,
2197 		NewMessageType type) {
2198 	return addNewMessage(IdFromMessage(data), data, localFlags, type);
2199 }
2200 
addNewMessage(MsgId id,const MTPMessage & data,MessageFlags localFlags,NewMessageType type)2201 HistoryItem *Session::addNewMessage(
2202 		MsgId id,
2203 		const MTPMessage &data,
2204 		MessageFlags localFlags,
2205 		NewMessageType type) {
2206 	const auto peerId = PeerFromMessage(data);
2207 	if (!peerId) {
2208 		return nullptr;
2209 	}
2210 
2211 	const auto result = history(peerId)->addNewMessage(
2212 		id,
2213 		data,
2214 		localFlags,
2215 		type);
2216 	if (type == NewMessageType::Unread) {
2217 		CheckForSwitchInlineButton(result);
2218 	}
2219 	return result;
2220 }
2221 
unreadBadge() const2222 int Session::unreadBadge() const {
2223 	return computeUnreadBadge(_chatsList.unreadState());
2224 }
2225 
unreadBadgeMuted() const2226 bool Session::unreadBadgeMuted() const {
2227 	return computeUnreadBadgeMuted(_chatsList.unreadState());
2228 }
2229 
unreadBadgeIgnoreOne(const Dialogs::Key & key) const2230 int Session::unreadBadgeIgnoreOne(const Dialogs::Key &key) const {
2231 	const auto remove = (key && key.entry()->inChatList())
2232 		? key.entry()->chatListUnreadState()
2233 		: Dialogs::UnreadState();
2234 	return computeUnreadBadge(_chatsList.unreadState() - remove);
2235 }
2236 
unreadBadgeMutedIgnoreOne(const Dialogs::Key & key) const2237 bool Session::unreadBadgeMutedIgnoreOne(const Dialogs::Key &key) const {
2238 	if (!Core::App().settings().includeMutedCounter()) {
2239 		return false;
2240 	}
2241 	const auto remove = (key && key.entry()->inChatList())
2242 		? key.entry()->chatListUnreadState()
2243 		: Dialogs::UnreadState();
2244 	return computeUnreadBadgeMuted(_chatsList.unreadState() - remove);
2245 }
2246 
unreadOnlyMutedBadge() const2247 int Session::unreadOnlyMutedBadge() const {
2248 	const auto state = _chatsList.unreadState();
2249 	return Core::App().settings().countUnreadMessages()
2250 		? state.messagesMuted
2251 		: state.chatsMuted;
2252 }
2253 
unreadBadgeChanges() const2254 rpl::producer<> Session::unreadBadgeChanges() const {
2255 	return _unreadBadgeChanges.events();
2256 }
2257 
notifyUnreadBadgeChanged()2258 void Session::notifyUnreadBadgeChanged() {
2259 	_unreadBadgeChanges.fire({});
2260 }
2261 
countUnreadRepliesLocally(not_null<HistoryItem * > root,MsgId afterId) const2262 std::optional<int> Session::countUnreadRepliesLocally(
2263 		not_null<HistoryItem*> root,
2264 		MsgId afterId) const {
2265 	auto result = std::optional<int>();
2266 	_unreadRepliesCountRequests.fire({
2267 		.root = root,
2268 		.afterId = afterId,
2269 		.result = &result,
2270 	});
2271 	return result;
2272 }
2273 
unreadRepliesCountRequests() const2274 auto Session::unreadRepliesCountRequests() const
2275 -> rpl::producer<UnreadRepliesCountRequest> {
2276 	return _unreadRepliesCountRequests.events();
2277 }
2278 
computeUnreadBadge(const Dialogs::UnreadState & state) const2279 int Session::computeUnreadBadge(const Dialogs::UnreadState &state) const {
2280 	const auto all = Core::App().settings().includeMutedCounter();
2281 	return std::max(state.marks - (all ? 0 : state.marksMuted), 0)
2282 		+ (Core::App().settings().countUnreadMessages()
2283 			? std::max(state.messages - (all ? 0 : state.messagesMuted), 0)
2284 			: std::max(state.chats - (all ? 0 : state.chatsMuted), 0));
2285 }
2286 
computeUnreadBadgeMuted(const Dialogs::UnreadState & state) const2287 bool Session::computeUnreadBadgeMuted(
2288 		const Dialogs::UnreadState &state) const {
2289 	if (!Core::App().settings().includeMutedCounter()) {
2290 		return false;
2291 	}
2292 	return (state.marksMuted >= state.marks)
2293 		&& (Core::App().settings().countUnreadMessages()
2294 			? (state.messagesMuted >= state.messages)
2295 			: (state.chatsMuted >= state.chats));
2296 }
2297 
selfDestructIn(not_null<HistoryItem * > item,crl::time delay)2298 void Session::selfDestructIn(not_null<HistoryItem*> item, crl::time delay) {
2299 	_selfDestructItems.push_back(item->fullId());
2300 	if (!_selfDestructTimer.isActive()
2301 		|| _selfDestructTimer.remainingTime() > delay) {
2302 		_selfDestructTimer.callOnce(delay);
2303 	}
2304 }
2305 
checkSelfDestructItems()2306 void Session::checkSelfDestructItems() {
2307 	const auto now = crl::now();
2308 	auto nextDestructIn = crl::time(0);
2309 	for (auto i = _selfDestructItems.begin(); i != _selfDestructItems.cend();) {
2310 		if (const auto item = message(*i)) {
2311 			if (auto destructIn = item->getSelfDestructIn(now)) {
2312 				if (nextDestructIn > 0) {
2313 					accumulate_min(nextDestructIn, destructIn);
2314 				} else {
2315 					nextDestructIn = destructIn;
2316 				}
2317 				++i;
2318 			} else {
2319 				i = _selfDestructItems.erase(i);
2320 			}
2321 		} else {
2322 			i = _selfDestructItems.erase(i);
2323 		}
2324 	}
2325 	if (nextDestructIn > 0) {
2326 		_selfDestructTimer.callOnce(nextDestructIn);
2327 	}
2328 }
2329 
photo(PhotoId id)2330 not_null<PhotoData*> Session::photo(PhotoId id) {
2331 	auto i = _photos.find(id);
2332 	if (i == _photos.end()) {
2333 		i = _photos.emplace(
2334 			id,
2335 			std::make_unique<PhotoData>(this, id)).first;
2336 	}
2337 	return i->second.get();
2338 }
2339 
processPhoto(const MTPPhoto & data)2340 not_null<PhotoData*> Session::processPhoto(const MTPPhoto &data) {
2341 	return data.match([&](const MTPDphoto &data) {
2342 		return processPhoto(data);
2343 	}, [&](const MTPDphotoEmpty &data) {
2344 		return photo(data.vid().v);
2345 	});
2346 }
2347 
processPhoto(const MTPDphoto & data)2348 not_null<PhotoData*> Session::processPhoto(const MTPDphoto &data) {
2349 	const auto result = photo(data.vid().v);
2350 	photoApplyFields(result, data);
2351 	return result;
2352 }
2353 
processPhoto(const MTPPhoto & data,const PreparedPhotoThumbs & thumbs)2354 not_null<PhotoData*> Session::processPhoto(
2355 		const MTPPhoto &data,
2356 		const PreparedPhotoThumbs &thumbs) {
2357 	Expects(!thumbs.empty());
2358 
2359 	const auto find = [&](const QByteArray &levels) {
2360 		const auto kInvalidIndex = int(levels.size());
2361 		const auto level = [&](const auto &pair) {
2362 			const auto letter = pair.first;
2363 			const auto index = levels.indexOf(letter);
2364 			return (index >= 0) ? index : kInvalidIndex;
2365 		};
2366 		const auto result = ranges::max_element(
2367 			thumbs,
2368 			std::greater<>(),
2369 			level);
2370 		return (level(*result) == kInvalidIndex) ? thumbs.end() : result;
2371 	};
2372 	const auto image = [&](const QByteArray &levels) {
2373 		const auto i = find(levels);
2374 		return (i == thumbs.end())
2375 			? ImageWithLocation()
2376 			: Images::FromImageInMemory(
2377 				i->second.image,
2378 				"JPG",
2379 				i->second.bytes);
2380 	};
2381 	const auto small = image(SmallLevels);
2382 	const auto thumbnail = image(ThumbnailLevels);
2383 	const auto large = image(LargeLevels);
2384 	return data.match([&](const MTPDphoto &data) {
2385 		return photo(
2386 			data.vid().v,
2387 			data.vaccess_hash().v,
2388 			data.vfile_reference().v,
2389 			data.vdate().v,
2390 			data.vdc_id().v,
2391 			data.is_has_stickers(),
2392 			QByteArray(),
2393 			small,
2394 			thumbnail,
2395 			large,
2396 			ImageWithLocation{},
2397 			crl::time(0));
2398 	}, [&](const MTPDphotoEmpty &data) {
2399 		return photo(data.vid().v);
2400 	});
2401 }
2402 
photo(PhotoId id,const uint64 & access,const QByteArray & fileReference,TimeId date,int32 dc,bool hasStickers,const QByteArray & inlineThumbnailBytes,const ImageWithLocation & small,const ImageWithLocation & thumbnail,const ImageWithLocation & large,const ImageWithLocation & video,crl::time videoStartTime)2403 not_null<PhotoData*> Session::photo(
2404 		PhotoId id,
2405 		const uint64 &access,
2406 		const QByteArray &fileReference,
2407 		TimeId date,
2408 		int32 dc,
2409 		bool hasStickers,
2410 		const QByteArray &inlineThumbnailBytes,
2411 		const ImageWithLocation &small,
2412 		const ImageWithLocation &thumbnail,
2413 		const ImageWithLocation &large,
2414 		const ImageWithLocation &video,
2415 		crl::time videoStartTime) {
2416 	const auto result = photo(id);
2417 	photoApplyFields(
2418 		result,
2419 		access,
2420 		fileReference,
2421 		date,
2422 		dc,
2423 		hasStickers,
2424 		inlineThumbnailBytes,
2425 		small,
2426 		thumbnail,
2427 		large,
2428 		video,
2429 		videoStartTime);
2430 	return result;
2431 }
2432 
photoConvert(not_null<PhotoData * > original,const MTPPhoto & data)2433 void Session::photoConvert(
2434 		not_null<PhotoData*> original,
2435 		const MTPPhoto &data) {
2436 	const auto id = data.match([](const auto &data) {
2437 		return data.vid().v;
2438 	});
2439 	const auto idChanged = (original->id != id);
2440 	if (idChanged) {
2441 		auto i = _photos.find(id);
2442 		if (i == _photos.end()) {
2443 			const auto j = _photos.find(original->id);
2444 			Assert(j != _photos.end());
2445 			auto owned = std::move(j->second);
2446 			_photos.erase(j);
2447 			i = _photos.emplace(id, std::move(owned)).first;
2448 		}
2449 
2450 		original->id = id;
2451 		original->uploadingData = nullptr;
2452 
2453 		if (i->second.get() != original) {
2454 			photoApplyFields(i->second.get(), data);
2455 		}
2456 	}
2457 	photoApplyFields(original, data);
2458 }
2459 
photoFromWeb(const MTPWebDocument & data,const ImageLocation & thumbnailLocation)2460 PhotoData *Session::photoFromWeb(
2461 		const MTPWebDocument &data,
2462 		const ImageLocation &thumbnailLocation) {
2463 	const auto large = Images::FromWebDocument(data);
2464 	if (!large.valid()) {
2465 		return nullptr;
2466 	}
2467 	return photo(
2468 		base::RandomValue<PhotoId>(),
2469 		uint64(0),
2470 		QByteArray(),
2471 		base::unixtime::now(),
2472 		0,
2473 		false,
2474 		QByteArray(),
2475 		ImageWithLocation{},
2476 		ImageWithLocation{ .location = thumbnailLocation },
2477 		ImageWithLocation{ .location = large },
2478 		ImageWithLocation{},
2479 		crl::time(0));
2480 }
2481 
photoApplyFields(not_null<PhotoData * > photo,const MTPPhoto & data)2482 void Session::photoApplyFields(
2483 		not_null<PhotoData*> photo,
2484 		const MTPPhoto &data) {
2485 	if (data.type() == mtpc_photo) {
2486 		photoApplyFields(photo, data.c_photo());
2487 	}
2488 }
2489 
photoApplyFields(not_null<PhotoData * > photo,const MTPDphoto & data)2490 void Session::photoApplyFields(
2491 		not_null<PhotoData*> photo,
2492 		const MTPDphoto &data) {
2493 	const auto &sizes = data.vsizes().v;
2494 	const auto progressive = [&] {
2495 		const auto area = [&](const MTPPhotoSize &size) {
2496 			return size.match([](const MTPDphotoSizeProgressive &data) {
2497 				return data.vw().v * data.vh().v;
2498 			}, [](const auto &) {
2499 				return 0;
2500 			});
2501 		};
2502 		const auto found = ranges::max_element(sizes, std::less<>(), area);
2503 		return (found == sizes.end()
2504 			|| found->type() != mtpc_photoSizeProgressive)
2505 			? sizes.end()
2506 			: found;
2507 	}();
2508 	const auto find = [&](const QByteArray &levels) {
2509 		const auto kInvalidIndex = int(levels.size());
2510 		const auto level = [&](const MTPPhotoSize &size) {
2511 			const auto letter = size.match([](const MTPDphotoSizeEmpty &) {
2512 				return char(0);
2513 			}, [](const auto &size) {
2514 				return size.vtype().v.isEmpty() ? char(0) : size.vtype().v[0];
2515 			});
2516 			const auto index = levels.indexOf(letter);
2517 			return (index >= 0) ? index : kInvalidIndex;
2518 		};
2519 		const auto result = ranges::max_element(
2520 			sizes,
2521 			std::greater<>(),
2522 			level);
2523 		return (level(*result) == kInvalidIndex) ? sizes.end() : result;
2524 	};
2525 	const auto image = [&](const QByteArray &levels) {
2526 		const auto i = find(levels);
2527 		return (i == sizes.end())
2528 			? ImageWithLocation()
2529 			: Images::FromPhotoSize(_session, data, *i);
2530 	};
2531 	const auto findVideoSize = [&]() -> std::optional<MTPVideoSize> {
2532 		const auto sizes = data.vvideo_sizes();
2533 		if (!sizes || sizes->v.isEmpty()) {
2534 			return std::nullopt;
2535 		}
2536 		const auto area = [](const MTPVideoSize &size) {
2537 			return size.match([](const MTPDvideoSize &data) {
2538 				return data.vsize().v ? (data.vw().v * data.vh().v) : 0;
2539 			});
2540 		};
2541 		const auto result = *ranges::max_element(
2542 			sizes->v,
2543 			std::greater<>(),
2544 			area);
2545 		return (area(result) > 0) ? std::make_optional(result) : std::nullopt;
2546 	};
2547 	const auto useProgressive = (progressive != sizes.end());
2548 	const auto large = useProgressive
2549 		? Images::FromPhotoSize(_session, data, *progressive)
2550 		: image(LargeLevels);
2551 	if (large.location.valid()) {
2552 		const auto video = findVideoSize();
2553 		photoApplyFields(
2554 			photo,
2555 			data.vaccess_hash().v,
2556 			data.vfile_reference().v,
2557 			data.vdate().v,
2558 			data.vdc_id().v,
2559 			data.is_has_stickers(),
2560 			FindPhotoInlineThumbnail(data),
2561 			(useProgressive
2562 				? ImageWithLocation()
2563 				: image(SmallLevels)),
2564 			(useProgressive
2565 				? Images::FromProgressiveSize(_session, *progressive, 1)
2566 				: image(ThumbnailLevels)),
2567 			large,
2568 			(video
2569 				? Images::FromVideoSize(_session, data, *video)
2570 				: ImageWithLocation()),
2571 			(video
2572 				? VideoStartTime(
2573 					*video->match([](const auto &data) { return &data; }))
2574 				: 0));
2575 	}
2576 }
2577 
photoApplyFields(not_null<PhotoData * > photo,const uint64 & access,const QByteArray & fileReference,TimeId date,int32 dc,bool hasStickers,const QByteArray & inlineThumbnailBytes,const ImageWithLocation & small,const ImageWithLocation & thumbnail,const ImageWithLocation & large,const ImageWithLocation & video,crl::time videoStartTime)2578 void Session::photoApplyFields(
2579 		not_null<PhotoData*> photo,
2580 		const uint64 &access,
2581 		const QByteArray &fileReference,
2582 		TimeId date,
2583 		int32 dc,
2584 		bool hasStickers,
2585 		const QByteArray &inlineThumbnailBytes,
2586 		const ImageWithLocation &small,
2587 		const ImageWithLocation &thumbnail,
2588 		const ImageWithLocation &large,
2589 		const ImageWithLocation &video,
2590 		crl::time videoStartTime) {
2591 	if (!date) {
2592 		return;
2593 	}
2594 	photo->setRemoteLocation(dc, access, fileReference);
2595 	photo->date = date;
2596 	photo->setHasAttachedStickers(hasStickers);
2597 	photo->updateImages(
2598 		inlineThumbnailBytes,
2599 		small,
2600 		thumbnail,
2601 		large,
2602 		video,
2603 		videoStartTime);
2604 }
2605 
document(DocumentId id)2606 not_null<DocumentData*> Session::document(DocumentId id) {
2607 	auto i = _documents.find(id);
2608 	if (i == _documents.cend()) {
2609 		i = _documents.emplace(
2610 			id,
2611 			std::make_unique<DocumentData>(this, id)).first;
2612 	}
2613 	return i->second.get();
2614 }
2615 
processDocument(const MTPDocument & data)2616 not_null<DocumentData*> Session::processDocument(const MTPDocument &data) {
2617 	return data.match([&](const MTPDdocument &data) {
2618 		return processDocument(data);
2619 	}, [&](const MTPDdocumentEmpty &data) {
2620 		return document(data.vid().v);
2621 	});
2622 }
2623 
processDocument(const MTPDdocument & data)2624 not_null<DocumentData*> Session::processDocument(const MTPDdocument &data) {
2625 	const auto result = document(data.vid().v);
2626 	documentApplyFields(result, data);
2627 	return result;
2628 }
2629 
processDocument(const MTPdocument & data,const ImageWithLocation & thumbnail)2630 not_null<DocumentData*> Session::processDocument(
2631 		const MTPdocument &data,
2632 		const ImageWithLocation &thumbnail) {
2633 	return data.match([&](const MTPDdocument &data) {
2634 		return document(
2635 			data.vid().v,
2636 			data.vaccess_hash().v,
2637 			data.vfile_reference().v,
2638 			data.vdate().v,
2639 			data.vattributes().v,
2640 			qs(data.vmime_type()),
2641 			InlineImageLocation(),
2642 			thumbnail,
2643 			ImageWithLocation(),
2644 			data.vdc_id().v,
2645 			data.vsize().v);
2646 	}, [&](const MTPDdocumentEmpty &data) {
2647 		return document(data.vid().v);
2648 	});
2649 }
2650 
document(DocumentId id,const uint64 & access,const QByteArray & fileReference,TimeId date,const QVector<MTPDocumentAttribute> & attributes,const QString & mime,const InlineImageLocation & inlineThumbnail,const ImageWithLocation & thumbnail,const ImageWithLocation & videoThumbnail,int32 dc,int32 size)2651 not_null<DocumentData*> Session::document(
2652 		DocumentId id,
2653 		const uint64 &access,
2654 		const QByteArray &fileReference,
2655 		TimeId date,
2656 		const QVector<MTPDocumentAttribute> &attributes,
2657 		const QString &mime,
2658 		const InlineImageLocation &inlineThumbnail,
2659 		const ImageWithLocation &thumbnail,
2660 		const ImageWithLocation &videoThumbnail,
2661 		int32 dc,
2662 		int32 size) {
2663 	const auto result = document(id);
2664 	documentApplyFields(
2665 		result,
2666 		access,
2667 		fileReference,
2668 		date,
2669 		attributes,
2670 		mime,
2671 		inlineThumbnail,
2672 		thumbnail,
2673 		videoThumbnail,
2674 		dc,
2675 		size);
2676 	return result;
2677 }
2678 
documentConvert(not_null<DocumentData * > original,const MTPDocument & data)2679 void Session::documentConvert(
2680 		not_null<DocumentData*> original,
2681 		const MTPDocument &data) {
2682 	const auto id = data.match([](const auto &data) {
2683 		return data.vid().v;
2684 	});
2685 	const auto oldCacheKey = original->cacheKey();
2686 	const auto oldGoodKey = original->goodThumbnailCacheKey();
2687 	const auto idChanged = (original->id != id);
2688 	if (idChanged) {
2689 		auto i = _documents.find(id);
2690 		if (i == _documents.end()) {
2691 			const auto j = _documents.find(original->id);
2692 			Assert(j != _documents.end());
2693 			auto owned = std::move(j->second);
2694 			_documents.erase(j);
2695 			i = _documents.emplace(id, std::move(owned)).first;
2696 		}
2697 
2698 		original->id = id;
2699 		original->status = FileReady;
2700 		original->uploadingData = nullptr;
2701 
2702 		if (i->second.get() != original) {
2703 			documentApplyFields(i->second.get(), data);
2704 		}
2705 	}
2706 	documentApplyFields(original, data);
2707 	if (idChanged) {
2708 		cache().moveIfEmpty(oldCacheKey, original->cacheKey());
2709 		cache().moveIfEmpty(oldGoodKey, original->goodThumbnailCacheKey());
2710 		if (stickers().savedGifs().indexOf(original) >= 0) {
2711 			_session->local().writeSavedGifs();
2712 		}
2713 	}
2714 }
2715 
documentFromWeb(const MTPWebDocument & data,const ImageLocation & thumbnailLocation,const ImageLocation & videoThumbnailLocation)2716 DocumentData *Session::documentFromWeb(
2717 		const MTPWebDocument &data,
2718 		const ImageLocation &thumbnailLocation,
2719 		const ImageLocation &videoThumbnailLocation) {
2720 	return data.match([&](const auto &data) {
2721 		return documentFromWeb(
2722 			data,
2723 			thumbnailLocation,
2724 			videoThumbnailLocation);
2725 	});
2726 }
2727 
documentFromWeb(const MTPDwebDocument & data,const ImageLocation & thumbnailLocation,const ImageLocation & videoThumbnailLocation)2728 DocumentData *Session::documentFromWeb(
2729 		const MTPDwebDocument &data,
2730 		const ImageLocation &thumbnailLocation,
2731 		const ImageLocation &videoThumbnailLocation) {
2732 	const auto result = document(
2733 		base::RandomValue<DocumentId>(),
2734 		uint64(0),
2735 		QByteArray(),
2736 		base::unixtime::now(),
2737 		data.vattributes().v,
2738 		data.vmime_type().v,
2739 		InlineImageLocation(),
2740 		ImageWithLocation{ .location = thumbnailLocation },
2741 		ImageWithLocation{ .location = videoThumbnailLocation },
2742 		session().mainDcId(),
2743 		int32(0)); // data.vsize().v
2744 	result->setWebLocation(WebFileLocation(
2745 		data.vurl().v,
2746 		data.vaccess_hash().v));
2747 	return result;
2748 }
2749 
documentFromWeb(const MTPDwebDocumentNoProxy & data,const ImageLocation & thumbnailLocation,const ImageLocation & videoThumbnailLocation)2750 DocumentData *Session::documentFromWeb(
2751 		const MTPDwebDocumentNoProxy &data,
2752 		const ImageLocation &thumbnailLocation,
2753 		const ImageLocation &videoThumbnailLocation) {
2754 	const auto result = document(
2755 		base::RandomValue<DocumentId>(),
2756 		uint64(0),
2757 		QByteArray(),
2758 		base::unixtime::now(),
2759 		data.vattributes().v,
2760 		data.vmime_type().v,
2761 		InlineImageLocation(),
2762 		ImageWithLocation{ .location = thumbnailLocation },
2763 		ImageWithLocation{ .location = videoThumbnailLocation },
2764 		session().mainDcId(),
2765 		int32(0)); // data.vsize().v
2766 	result->setContentUrl(qs(data.vurl()));
2767 	return result;
2768 }
2769 
documentApplyFields(not_null<DocumentData * > document,const MTPDocument & data)2770 void Session::documentApplyFields(
2771 		not_null<DocumentData*> document,
2772 		const MTPDocument &data) {
2773 	if (data.type() == mtpc_document) {
2774 		documentApplyFields(document, data.c_document());
2775 	}
2776 }
2777 
documentApplyFields(not_null<DocumentData * > document,const MTPDdocument & data)2778 void Session::documentApplyFields(
2779 		not_null<DocumentData*> document,
2780 		const MTPDdocument &data) {
2781 	const auto inlineThumbnail = FindDocumentInlineThumbnail(data);
2782 	const auto thumbnailSize = FindDocumentThumbnail(data);
2783 	const auto videoThumbnailSize = FindDocumentVideoThumbnail(data);
2784 	const auto prepared = Images::FromPhotoSize(
2785 		_session,
2786 		data,
2787 		thumbnailSize);
2788 	const auto videoThumbnail = videoThumbnailSize
2789 		? Images::FromVideoSize(_session, data, *videoThumbnailSize)
2790 		: ImageWithLocation();
2791 	documentApplyFields(
2792 		document,
2793 		data.vaccess_hash().v,
2794 		data.vfile_reference().v,
2795 		data.vdate().v,
2796 		data.vattributes().v,
2797 		qs(data.vmime_type()),
2798 		inlineThumbnail,
2799 		prepared,
2800 		videoThumbnail,
2801 		data.vdc_id().v,
2802 		data.vsize().v);
2803 }
2804 
documentApplyFields(not_null<DocumentData * > document,const uint64 & access,const QByteArray & fileReference,TimeId date,const QVector<MTPDocumentAttribute> & attributes,const QString & mime,const InlineImageLocation & inlineThumbnail,const ImageWithLocation & thumbnail,const ImageWithLocation & videoThumbnail,int32 dc,int32 size)2805 void Session::documentApplyFields(
2806 		not_null<DocumentData*> document,
2807 		const uint64 &access,
2808 		const QByteArray &fileReference,
2809 		TimeId date,
2810 		const QVector<MTPDocumentAttribute> &attributes,
2811 		const QString &mime,
2812 		const InlineImageLocation &inlineThumbnail,
2813 		const ImageWithLocation &thumbnail,
2814 		const ImageWithLocation &videoThumbnail,
2815 		int32 dc,
2816 		int32 size) {
2817 	if (!date) {
2818 		return;
2819 	}
2820 	document->date = date;
2821 	document->setMimeString(mime);
2822 	document->updateThumbnails(
2823 		inlineThumbnail,
2824 		thumbnail,
2825 		videoThumbnail);
2826 	document->size = size;
2827 	document->setattributes(attributes);
2828 
2829 	// Uses 'type' that is computed from attributes.
2830 	document->recountIsImage();
2831 	if (dc != 0 && access != 0) {
2832 		document->setRemoteLocation(dc, access, fileReference);
2833 	}
2834 }
2835 
webpage(WebPageId id)2836 not_null<WebPageData*> Session::webpage(WebPageId id) {
2837 	auto i = _webpages.find(id);
2838 	if (i == _webpages.cend()) {
2839 		i = _webpages.emplace(
2840 			id,
2841 			std::make_unique<WebPageData>(this, id)).first;
2842 	}
2843 	return i->second.get();
2844 }
2845 
processWebpage(const MTPWebPage & data)2846 not_null<WebPageData*> Session::processWebpage(const MTPWebPage &data) {
2847 	switch (data.type()) {
2848 	case mtpc_webPage:
2849 		return processWebpage(data.c_webPage());
2850 	case mtpc_webPageEmpty: {
2851 		const auto result = webpage(data.c_webPageEmpty().vid().v);
2852 		if (result->pendingTill > 0) {
2853 			result->pendingTill = -1; // failed
2854 			notifyWebPageUpdateDelayed(result);
2855 		}
2856 		return result;
2857 	} break;
2858 	case mtpc_webPagePending:
2859 		return processWebpage(data.c_webPagePending());
2860 	case mtpc_webPageNotModified:
2861 		LOG(("API Error: "
2862 			"webPageNotModified is unexpected in Session::webpage()."));
2863 		return webpage(0);
2864 	}
2865 	Unexpected("Type in Session::webpage().");
2866 }
2867 
processWebpage(const MTPDwebPage & data)2868 not_null<WebPageData*> Session::processWebpage(const MTPDwebPage &data) {
2869 	const auto result = webpage(data.vid().v);
2870 	webpageApplyFields(result, data);
2871 	return result;
2872 }
2873 
processWebpage(const MTPDwebPagePending & data)2874 not_null<WebPageData*> Session::processWebpage(const MTPDwebPagePending &data) {
2875 	constexpr auto kDefaultPendingTimeout = 60;
2876 	const auto result = webpage(data.vid().v);
2877 	webpageApplyFields(
2878 		result,
2879 		WebPageType::Article,
2880 		QString(),
2881 		QString(),
2882 		QString(),
2883 		QString(),
2884 		TextWithEntities(),
2885 		nullptr,
2886 		nullptr,
2887 		WebPageCollage(),
2888 		0,
2889 		QString(),
2890 		data.vdate().v
2891 			? data.vdate().v
2892 			: (base::unixtime::now() + kDefaultPendingTimeout));
2893 	return result;
2894 }
2895 
webpage(WebPageId id,const QString & siteName,const TextWithEntities & content)2896 not_null<WebPageData*> Session::webpage(
2897 		WebPageId id,
2898 		const QString &siteName,
2899 		const TextWithEntities &content) {
2900 	return webpage(
2901 		id,
2902 		WebPageType::Article,
2903 		QString(),
2904 		QString(),
2905 		siteName,
2906 		QString(),
2907 		content,
2908 		nullptr,
2909 		nullptr,
2910 		WebPageCollage(),
2911 		0,
2912 		QString(),
2913 		TimeId(0));
2914 }
2915 
webpage(WebPageId id,WebPageType type,const QString & url,const QString & displayUrl,const QString & siteName,const QString & title,const TextWithEntities & description,PhotoData * photo,DocumentData * document,WebPageCollage && collage,int duration,const QString & author,TimeId pendingTill)2916 not_null<WebPageData*> Session::webpage(
2917 		WebPageId id,
2918 		WebPageType type,
2919 		const QString &url,
2920 		const QString &displayUrl,
2921 		const QString &siteName,
2922 		const QString &title,
2923 		const TextWithEntities &description,
2924 		PhotoData *photo,
2925 		DocumentData *document,
2926 		WebPageCollage &&collage,
2927 		int duration,
2928 		const QString &author,
2929 		TimeId pendingTill) {
2930 	const auto result = webpage(id);
2931 	webpageApplyFields(
2932 		result,
2933 		type,
2934 		url,
2935 		displayUrl,
2936 		siteName,
2937 		title,
2938 		description,
2939 		photo,
2940 		document,
2941 		std::move(collage),
2942 		duration,
2943 		author,
2944 		pendingTill);
2945 	return result;
2946 }
2947 
webpageApplyFields(not_null<WebPageData * > page,const MTPDwebPage & data)2948 void Session::webpageApplyFields(
2949 		not_null<WebPageData*> page,
2950 		const MTPDwebPage &data) {
2951 	auto description = TextWithEntities {
2952 		TextUtilities::Clean(qs(data.vdescription().value_or_empty()))
2953 	};
2954 	const auto siteName = qs(data.vsite_name().value_or_empty());
2955 	auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText;
2956 	if (siteName == qstr("Twitter") || siteName == qstr("Instagram")) {
2957 		parseFlags |= TextParseHashtags | TextParseMentions;
2958 	}
2959 	TextUtilities::ParseEntities(description, parseFlags);
2960 	const auto pendingTill = TimeId(0);
2961 	const auto photo = data.vphoto();
2962 	const auto document = data.vdocument();
2963 	const auto lookupInAttribute = [&](
2964 			const MTPDwebPageAttributeTheme &data) -> DocumentData* {
2965 		if (const auto documents = data.vdocuments()) {
2966 			for (const auto &document : documents->v) {
2967 				const auto processed = processDocument(document);
2968 				if (processed->isTheme()) {
2969 					return processed;
2970 				}
2971 			}
2972 		}
2973 		return nullptr;
2974 	};
2975 	const auto lookupThemeDocument = [&]() -> DocumentData* {
2976 		if (const auto attributes = data.vattributes()) {
2977 			for (const auto &attribute : attributes->v) {
2978 				const auto result = attribute.match([&](
2979 						const MTPDwebPageAttributeTheme &data) {
2980 					return lookupInAttribute(data);
2981 				});
2982 				if (result) {
2983 					return result;
2984 				}
2985 			}
2986 		}
2987 		return nullptr;
2988 	};
2989 	webpageApplyFields(
2990 		page,
2991 		ParseWebPageType(data),
2992 		qs(data.vurl()),
2993 		qs(data.vdisplay_url()),
2994 		siteName,
2995 		qs(data.vtitle().value_or_empty()),
2996 		description,
2997 		photo ? processPhoto(*photo).get() : nullptr,
2998 		document ? processDocument(*document).get() : lookupThemeDocument(),
2999 		WebPageCollage(this, data),
3000 		data.vduration().value_or_empty(),
3001 		qs(data.vauthor().value_or_empty()),
3002 		pendingTill);
3003 }
3004 
webpageApplyFields(not_null<WebPageData * > page,WebPageType type,const QString & url,const QString & displayUrl,const QString & siteName,const QString & title,const TextWithEntities & description,PhotoData * photo,DocumentData * document,WebPageCollage && collage,int duration,const QString & author,TimeId pendingTill)3005 void Session::webpageApplyFields(
3006 		not_null<WebPageData*> page,
3007 		WebPageType type,
3008 		const QString &url,
3009 		const QString &displayUrl,
3010 		const QString &siteName,
3011 		const QString &title,
3012 		const TextWithEntities &description,
3013 		PhotoData *photo,
3014 		DocumentData *document,
3015 		WebPageCollage &&collage,
3016 		int duration,
3017 		const QString &author,
3018 		TimeId pendingTill) {
3019 	const auto requestPending = (!page->pendingTill && pendingTill > 0);
3020 	const auto changed = page->applyChanges(
3021 		type,
3022 		url,
3023 		displayUrl,
3024 		siteName,
3025 		title,
3026 		description,
3027 		photo,
3028 		document,
3029 		std::move(collage),
3030 		duration,
3031 		author,
3032 		pendingTill);
3033 	if (requestPending) {
3034 		_session->api().requestWebPageDelayed(page);
3035 	}
3036 	if (changed) {
3037 		notifyWebPageUpdateDelayed(page);
3038 	}
3039 }
3040 
game(GameId id)3041 not_null<GameData*> Session::game(GameId id) {
3042 	auto i = _games.find(id);
3043 	if (i == _games.cend()) {
3044 		i = _games.emplace(id, std::make_unique<GameData>(this, id)).first;
3045 	}
3046 	return i->second.get();
3047 }
3048 
processGame(const MTPDgame & data)3049 not_null<GameData*> Session::processGame(const MTPDgame &data) {
3050 	const auto result = game(data.vid().v);
3051 	gameApplyFields(result, data);
3052 	return result;
3053 }
3054 
game(GameId id,const uint64 & accessHash,const QString & shortName,const QString & title,const QString & description,PhotoData * photo,DocumentData * document)3055 not_null<GameData*> Session::game(
3056 		GameId id,
3057 		const uint64 &accessHash,
3058 		const QString &shortName,
3059 		const QString &title,
3060 		const QString &description,
3061 		PhotoData *photo,
3062 		DocumentData *document) {
3063 	const auto result = game(id);
3064 	gameApplyFields(
3065 		result,
3066 		accessHash,
3067 		shortName,
3068 		title,
3069 		description,
3070 		photo,
3071 		document);
3072 	return result;
3073 }
3074 
gameConvert(not_null<GameData * > original,const MTPGame & data)3075 void Session::gameConvert(
3076 		not_null<GameData*> original,
3077 		const MTPGame &data) {
3078 	Expects(data.type() == mtpc_game);
3079 
3080 	const auto id = data.c_game().vid().v;
3081 	if (original->id != id) {
3082 		auto i = _games.find(id);
3083 		if (i == _games.end()) {
3084 			const auto j = _games.find(original->id);
3085 			Assert(j != _games.end());
3086 			auto owned = std::move(j->second);
3087 			_games.erase(j);
3088 			i = _games.emplace(id, std::move(owned)).first;
3089 		}
3090 
3091 		original->id = id;
3092 		original->accessHash = 0;
3093 
3094 		if (i->second.get() != original) {
3095 			gameApplyFields(i->second.get(), data.c_game());
3096 		}
3097 	}
3098 	gameApplyFields(original, data.c_game());
3099 }
3100 
gameApplyFields(not_null<GameData * > game,const MTPDgame & data)3101 void Session::gameApplyFields(
3102 		not_null<GameData*> game,
3103 		const MTPDgame &data) {
3104 	const auto document = data.vdocument();
3105 	gameApplyFields(
3106 		game,
3107 		data.vaccess_hash().v,
3108 		qs(data.vshort_name()),
3109 		qs(data.vtitle()),
3110 		qs(data.vdescription()),
3111 		processPhoto(data.vphoto()),
3112 		document ? processDocument(*document).get() : nullptr);
3113 }
3114 
gameApplyFields(not_null<GameData * > game,const uint64 & accessHash,const QString & shortName,const QString & title,const QString & description,PhotoData * photo,DocumentData * document)3115 void Session::gameApplyFields(
3116 		not_null<GameData*> game,
3117 		const uint64 &accessHash,
3118 		const QString &shortName,
3119 		const QString &title,
3120 		const QString &description,
3121 		PhotoData *photo,
3122 		DocumentData *document) {
3123 	if (game->accessHash) {
3124 		return;
3125 	}
3126 	game->accessHash = accessHash;
3127 	game->shortName = TextUtilities::Clean(shortName);
3128 	game->title = TextUtilities::SingleLine(title);
3129 	game->description = TextUtilities::Clean(description);
3130 	game->photo = photo;
3131 	game->document = document;
3132 	notifyGameUpdateDelayed(game);
3133 }
3134 
poll(PollId id)3135 not_null<PollData*> Session::poll(PollId id) {
3136 	auto i = _polls.find(id);
3137 	if (i == _polls.cend()) {
3138 		i = _polls.emplace(id, std::make_unique<PollData>(this, id)).first;
3139 	}
3140 	return i->second.get();
3141 }
3142 
processPoll(const MTPPoll & data)3143 not_null<PollData*> Session::processPoll(const MTPPoll &data) {
3144 	return data.match([&](const MTPDpoll &data) {
3145 		const auto id = data.vid().v;
3146 		const auto result = poll(id);
3147 		const auto changed = result->applyChanges(data);
3148 		if (changed) {
3149 			notifyPollUpdateDelayed(result);
3150 		}
3151 		if (result->closeDate > 0 && !result->closed()) {
3152 			_pollsClosings.emplace(result->closeDate, result);
3153 			checkPollsClosings();
3154 		}
3155 		return result;
3156 	});
3157 }
3158 
processPoll(const MTPDmessageMediaPoll & data)3159 not_null<PollData*> Session::processPoll(const MTPDmessageMediaPoll &data) {
3160 	const auto result = processPoll(data.vpoll());
3161 	const auto changed = result->applyResults(data.vresults());
3162 	if (changed) {
3163 		notifyPollUpdateDelayed(result);
3164 	}
3165 	return result;
3166 }
3167 
checkPollsClosings()3168 void Session::checkPollsClosings() {
3169 	const auto now = base::unixtime::now();
3170 	auto closest = 0;
3171 	for (auto i = _pollsClosings.begin(); i != _pollsClosings.end();) {
3172 		if (i->first <= now) {
3173 			if (i->second->closeByTimer()) {
3174 				notifyPollUpdateDelayed(i->second);
3175 			}
3176 			i = _pollsClosings.erase(i);
3177 		} else {
3178 			if (!closest) {
3179 				closest = i->first;
3180 			}
3181 			++i;
3182 		}
3183 	}
3184 	if (closest) {
3185 		_pollsClosingTimer.callOnce((closest - now) * crl::time(1000));
3186 	} else {
3187 		_pollsClosingTimer.cancel();
3188 	}
3189 }
3190 
applyUpdate(const MTPDupdateMessagePoll & update)3191 void Session::applyUpdate(const MTPDupdateMessagePoll &update) {
3192 	const auto updated = [&] {
3193 		const auto poll = update.vpoll();
3194 		const auto i = _polls.find(update.vpoll_id().v);
3195 		return (i == end(_polls))
3196 			? nullptr
3197 			: poll
3198 			? processPoll(*poll).get()
3199 			: i->second.get();
3200 	}();
3201 	if (updated && updated->applyResults(update.vresults())) {
3202 		notifyPollUpdateDelayed(updated);
3203 	}
3204 }
3205 
applyUpdate(const MTPDupdateChatParticipants & update)3206 void Session::applyUpdate(const MTPDupdateChatParticipants &update) {
3207 	const auto chatId = update.vparticipants().match([](const auto &update) {
3208 		return update.vchat_id().v;
3209 	});
3210 	if (const auto chat = chatLoaded(chatId)) {
3211 		ApplyChatUpdate(chat, update);
3212 		for (const auto &user : chat->participants) {
3213 			if (user->isBot() && !user->botInfo->inited) {
3214 				_session->api().requestFullPeer(user);
3215 			}
3216 		}
3217 	}
3218 }
3219 
applyUpdate(const MTPDupdateChatParticipantAdd & update)3220 void Session::applyUpdate(const MTPDupdateChatParticipantAdd &update) {
3221 	if (const auto chat = chatLoaded(update.vchat_id().v)) {
3222 		ApplyChatUpdate(chat, update);
3223 	}
3224 }
3225 
applyUpdate(const MTPDupdateChatParticipantDelete & update)3226 void Session::applyUpdate(const MTPDupdateChatParticipantDelete &update) {
3227 	if (const auto chat = chatLoaded(update.vchat_id().v)) {
3228 		ApplyChatUpdate(chat, update);
3229 	}
3230 }
3231 
applyUpdate(const MTPDupdateChatParticipantAdmin & update)3232 void Session::applyUpdate(const MTPDupdateChatParticipantAdmin &update) {
3233 	if (const auto chat = chatLoaded(update.vchat_id().v)) {
3234 		ApplyChatUpdate(chat, update);
3235 	}
3236 }
3237 
applyUpdate(const MTPDupdateChatDefaultBannedRights & update)3238 void Session::applyUpdate(const MTPDupdateChatDefaultBannedRights &update) {
3239 	if (const auto peer = peerLoaded(peerFromMTP(update.vpeer()))) {
3240 		if (const auto chat = peer->asChat()) {
3241 			ApplyChatUpdate(chat, update);
3242 		} else if (const auto channel = peer->asChannel()) {
3243 			ApplyChannelUpdate(channel, update);
3244 		} else {
3245 			LOG(("API Error: "
3246 				"User received in updateChatDefaultBannedRights."));
3247 		}
3248 	}
3249 }
3250 
location(const LocationPoint & point)3251 not_null<Data::CloudImage*> Session::location(const LocationPoint &point) {
3252 	const auto i = _locations.find(point);
3253 	if (i != _locations.cend()) {
3254 		return i->second.get();
3255 	}
3256 	const auto location = Data::ComputeLocation(point);
3257 	const auto prepared = ImageWithLocation{
3258 		.location = ImageLocation(
3259 			{ location },
3260 			location.width,
3261 			location.height)
3262 	};
3263 	return _locations.emplace(
3264 		point,
3265 		std::make_unique<Data::CloudImage>(
3266 			_session,
3267 			prepared)).first->second.get();
3268 }
3269 
registerPhotoItem(not_null<const PhotoData * > photo,not_null<HistoryItem * > item)3270 void Session::registerPhotoItem(
3271 		not_null<const PhotoData*> photo,
3272 		not_null<HistoryItem*> item) {
3273 	_photoItems[photo].insert(item);
3274 }
3275 
unregisterPhotoItem(not_null<const PhotoData * > photo,not_null<HistoryItem * > item)3276 void Session::unregisterPhotoItem(
3277 		not_null<const PhotoData*> photo,
3278 		not_null<HistoryItem*> item) {
3279 	const auto i = _photoItems.find(photo);
3280 	if (i != _photoItems.end()) {
3281 		auto &items = i->second;
3282 		if (items.remove(item) && items.empty()) {
3283 			_photoItems.erase(i);
3284 		}
3285 	}
3286 }
3287 
registerDocumentItem(not_null<const DocumentData * > document,not_null<HistoryItem * > item)3288 void Session::registerDocumentItem(
3289 		not_null<const DocumentData*> document,
3290 		not_null<HistoryItem*> item) {
3291 	_documentItems[document].insert(item);
3292 }
3293 
unregisterDocumentItem(not_null<const DocumentData * > document,not_null<HistoryItem * > item)3294 void Session::unregisterDocumentItem(
3295 		not_null<const DocumentData*> document,
3296 		not_null<HistoryItem*> item) {
3297 	const auto i = _documentItems.find(document);
3298 	if (i != _documentItems.end()) {
3299 		auto &items = i->second;
3300 		if (items.remove(item) && items.empty()) {
3301 			_documentItems.erase(i);
3302 		}
3303 	}
3304 }
3305 
registerWebPageView(not_null<const WebPageData * > page,not_null<ViewElement * > view)3306 void Session::registerWebPageView(
3307 		not_null<const WebPageData*> page,
3308 		not_null<ViewElement*> view) {
3309 	_webpageViews[page].insert(view);
3310 }
3311 
unregisterWebPageView(not_null<const WebPageData * > page,not_null<ViewElement * > view)3312 void Session::unregisterWebPageView(
3313 		not_null<const WebPageData*> page,
3314 		not_null<ViewElement*> view) {
3315 	const auto i = _webpageViews.find(page);
3316 	if (i != _webpageViews.end()) {
3317 		auto &items = i->second;
3318 		if (items.remove(view) && items.empty()) {
3319 			_webpageViews.erase(i);
3320 		}
3321 	}
3322 }
3323 
registerWebPageItem(not_null<const WebPageData * > page,not_null<HistoryItem * > item)3324 void Session::registerWebPageItem(
3325 		not_null<const WebPageData*> page,
3326 		not_null<HistoryItem*> item) {
3327 	_webpageItems[page].insert(item);
3328 }
3329 
unregisterWebPageItem(not_null<const WebPageData * > page,not_null<HistoryItem * > item)3330 void Session::unregisterWebPageItem(
3331 		not_null<const WebPageData*> page,
3332 		not_null<HistoryItem*> item) {
3333 	const auto i = _webpageItems.find(page);
3334 	if (i != _webpageItems.end()) {
3335 		auto &items = i->second;
3336 		if (items.remove(item) && items.empty()) {
3337 			_webpageItems.erase(i);
3338 		}
3339 	}
3340 }
3341 
registerGameView(not_null<const GameData * > game,not_null<ViewElement * > view)3342 void Session::registerGameView(
3343 		not_null<const GameData*> game,
3344 		not_null<ViewElement*> view) {
3345 	_gameViews[game].insert(view);
3346 }
3347 
unregisterGameView(not_null<const GameData * > game,not_null<ViewElement * > view)3348 void Session::unregisterGameView(
3349 		not_null<const GameData*> game,
3350 		not_null<ViewElement*> view) {
3351 	const auto i = _gameViews.find(game);
3352 	if (i != _gameViews.end()) {
3353 		auto &items = i->second;
3354 		if (items.remove(view) && items.empty()) {
3355 			_gameViews.erase(i);
3356 		}
3357 	}
3358 }
3359 
registerPollView(not_null<const PollData * > poll,not_null<ViewElement * > view)3360 void Session::registerPollView(
3361 		not_null<const PollData*> poll,
3362 		not_null<ViewElement*> view) {
3363 	_pollViews[poll].insert(view);
3364 }
3365 
unregisterPollView(not_null<const PollData * > poll,not_null<ViewElement * > view)3366 void Session::unregisterPollView(
3367 		not_null<const PollData*> poll,
3368 		not_null<ViewElement*> view) {
3369 	const auto i = _pollViews.find(poll);
3370 	if (i != _pollViews.end()) {
3371 		auto &items = i->second;
3372 		if (items.remove(view) && items.empty()) {
3373 			_pollViews.erase(i);
3374 		}
3375 	}
3376 }
3377 
registerContactView(UserId contactId,not_null<ViewElement * > view)3378 void Session::registerContactView(
3379 		UserId contactId,
3380 		not_null<ViewElement*> view) {
3381 	if (!contactId) {
3382 		return;
3383 	}
3384 	_contactViews[contactId].insert(view);
3385 }
3386 
unregisterContactView(UserId contactId,not_null<ViewElement * > view)3387 void Session::unregisterContactView(
3388 		UserId contactId,
3389 		not_null<ViewElement*> view) {
3390 	if (!contactId) {
3391 		return;
3392 	}
3393 	const auto i = _contactViews.find(contactId);
3394 	if (i != _contactViews.end()) {
3395 		auto &items = i->second;
3396 		if (items.remove(view) && items.empty()) {
3397 			_contactViews.erase(i);
3398 		}
3399 	}
3400 }
3401 
registerContactItem(UserId contactId,not_null<HistoryItem * > item)3402 void Session::registerContactItem(
3403 		UserId contactId,
3404 		not_null<HistoryItem*> item) {
3405 	if (!contactId) {
3406 		return;
3407 	}
3408 	const auto contact = userLoaded(contactId);
3409 	const auto canShare = contact ? contact->canShareThisContact() : false;
3410 
3411 	_contactItems[contactId].insert(item);
3412 
3413 	if (contact && canShare != contact->canShareThisContact()) {
3414 		session().changes().peerUpdated(
3415 			contact,
3416 			PeerUpdate::Flag::CanShareContact);
3417 	}
3418 
3419 	if (const auto i = _views.find(item); i != _views.end()) {
3420 		for (const auto view : i->second) {
3421 			if (const auto media = view->media()) {
3422 				media->updateSharedContactUserId(contactId);
3423 			}
3424 		}
3425 	}
3426 }
3427 
unregisterContactItem(UserId contactId,not_null<HistoryItem * > item)3428 void Session::unregisterContactItem(
3429 		UserId contactId,
3430 		not_null<HistoryItem*> item) {
3431 	if (!contactId) {
3432 		return;
3433 	}
3434 	const auto contact = userLoaded(contactId);
3435 	const auto canShare = contact ? contact->canShareThisContact() : false;
3436 
3437 	const auto i = _contactItems.find(contactId);
3438 	if (i != _contactItems.end()) {
3439 		auto &items = i->second;
3440 		if (items.remove(item) && items.empty()) {
3441 			_contactItems.erase(i);
3442 		}
3443 	}
3444 
3445 	if (contact && canShare != contact->canShareThisContact()) {
3446 		session().changes().peerUpdated(
3447 			contact,
3448 			PeerUpdate::Flag::CanShareContact);
3449 	}
3450 }
3451 
registerCallItem(not_null<HistoryItem * > item)3452 void Session::registerCallItem(not_null<HistoryItem*> item) {
3453 	_callItems.emplace(item);
3454 }
3455 
unregisterCallItem(not_null<HistoryItem * > item)3456 void Session::unregisterCallItem(not_null<HistoryItem*> item) {
3457 	_callItems.erase(item);
3458 }
3459 
destroyAllCallItems()3460 void Session::destroyAllCallItems() {
3461 	while (!_callItems.empty()) {
3462 		(*_callItems.begin())->destroy();
3463 	}
3464 }
3465 
documentMessageRemoved(not_null<DocumentData * > document)3466 void Session::documentMessageRemoved(not_null<DocumentData*> document) {
3467 	if (_documentItems.find(document) != _documentItems.end()) {
3468 		return;
3469 	}
3470 	if (document->loading()) {
3471 		document->cancel();
3472 	}
3473 }
3474 
checkPlayingAnimations()3475 void Session::checkPlayingAnimations() {
3476 	auto check = base::flat_set<not_null<ViewElement*>>();
3477 	for (const auto &view : _heavyViewParts) {
3478 		if (const auto media = view->media()) {
3479 			if (const auto document = media->getDocument()) {
3480 				if (document->isAnimation() || document->isVideoFile()) {
3481 					check.emplace(view);
3482 				}
3483 			} else if (const auto photo = media->getPhoto()) {
3484 				if (photo->hasVideo()) {
3485 					check.emplace(view);
3486 				}
3487 			}
3488 		}
3489 	}
3490 	for (const auto &view : check) {
3491 		view->media()->checkAnimation();
3492 	}
3493 }
3494 
findWebPageItem(not_null<WebPageData * > page) const3495 HistoryItem *Session::findWebPageItem(not_null<WebPageData*> page) const {
3496 	const auto i = _webpageItems.find(page);
3497 	if (i != _webpageItems.end()) {
3498 		for (const auto &item : i->second) {
3499 			if (item->isRegular()) {
3500 				return item;
3501 			}
3502 		}
3503 	}
3504 	return nullptr;
3505 }
3506 
findContactPhone(not_null<UserData * > contact) const3507 QString Session::findContactPhone(not_null<UserData*> contact) const {
3508 	const auto result = contact->phone();
3509 	return result.isEmpty()
3510 		? findContactPhone(peerToUser(contact->id))
3511 		: Ui::FormatPhone(result);
3512 }
3513 
findContactPhone(UserId contactId) const3514 QString Session::findContactPhone(UserId contactId) const {
3515 	const auto i = _contactItems.find(contactId);
3516 	if (i != _contactItems.end()) {
3517 		if (const auto media = (*begin(i->second))->media()) {
3518 			if (const auto contact = media->sharedContact()) {
3519 				return contact->phoneNumber;
3520 			}
3521 		}
3522 	}
3523 	return QString();
3524 }
3525 
hasPendingWebPageGamePollNotification() const3526 bool Session::hasPendingWebPageGamePollNotification() const {
3527 	return !_webpagesUpdated.empty()
3528 		|| !_gamesUpdated.empty()
3529 		|| !_pollsUpdated.empty();
3530 }
3531 
notifyWebPageUpdateDelayed(not_null<WebPageData * > page)3532 void Session::notifyWebPageUpdateDelayed(not_null<WebPageData*> page) {
3533 	const auto invoke = !hasPendingWebPageGamePollNotification();
3534 	_webpagesUpdated.insert(page);
3535 	if (invoke) {
3536 		crl::on_main(_session, [=] { sendWebPageGamePollNotifications(); });
3537 	}
3538 }
3539 
notifyGameUpdateDelayed(not_null<GameData * > game)3540 void Session::notifyGameUpdateDelayed(not_null<GameData*> game) {
3541 	const auto invoke = !hasPendingWebPageGamePollNotification();
3542 	_gamesUpdated.insert(game);
3543 	if (invoke) {
3544 		crl::on_main(_session, [=] { sendWebPageGamePollNotifications(); });
3545 	}
3546 }
3547 
notifyPollUpdateDelayed(not_null<PollData * > poll)3548 void Session::notifyPollUpdateDelayed(not_null<PollData*> poll) {
3549 	const auto invoke = !hasPendingWebPageGamePollNotification();
3550 	_pollsUpdated.insert(poll);
3551 	if (invoke) {
3552 		crl::on_main(_session, [=] { sendWebPageGamePollNotifications(); });
3553 	}
3554 }
3555 
sendWebPageGamePollNotifications()3556 void Session::sendWebPageGamePollNotifications() {
3557 	for (const auto &page : base::take(_webpagesUpdated)) {
3558 		_webpageUpdates.fire_copy(page);
3559 		const auto i = _webpageViews.find(page);
3560 		if (i != _webpageViews.end()) {
3561 			for (const auto &view : i->second) {
3562 				requestViewResize(view);
3563 			}
3564 		}
3565 	}
3566 	for (const auto &game : base::take(_gamesUpdated)) {
3567 		if (const auto i = _gameViews.find(game); i != _gameViews.end()) {
3568 			for (const auto &view : i->second) {
3569 				requestViewResize(view);
3570 			}
3571 		}
3572 	}
3573 	for (const auto &poll : base::take(_pollsUpdated)) {
3574 		if (const auto i = _pollViews.find(poll); i != _pollViews.end()) {
3575 			for (const auto &view : i->second) {
3576 				requestViewResize(view);
3577 			}
3578 		}
3579 	}
3580 }
3581 
webPageUpdates() const3582 rpl::producer<not_null<WebPageData*>> Session::webPageUpdates() const {
3583 	return _webpageUpdates.events();
3584 }
3585 
channelDifferenceTooLong(not_null<ChannelData * > channel)3586 void Session::channelDifferenceTooLong(not_null<ChannelData*> channel) {
3587 	_channelDifferenceTooLong.fire_copy(channel);
3588 }
3589 
channelDifferenceTooLong() const3590 rpl::producer<not_null<ChannelData*>> Session::channelDifferenceTooLong() const {
3591 	return _channelDifferenceTooLong.events();
3592 }
3593 
registerItemView(not_null<ViewElement * > view)3594 void Session::registerItemView(not_null<ViewElement*> view) {
3595 	_views[view->data()].push_back(view);
3596 }
3597 
unregisterItemView(not_null<ViewElement * > view)3598 void Session::unregisterItemView(not_null<ViewElement*> view) {
3599 	Expects(!_heavyViewParts.contains(view));
3600 
3601 	const auto i = _views.find(view->data());
3602 	if (i != end(_views)) {
3603 		auto &list = i->second;
3604 		list.erase(ranges::remove(list, view), end(list));
3605 		if (list.empty()) {
3606 			_views.erase(i);
3607 		}
3608 	}
3609 	if (App::hoveredItem() == view) {
3610 		App::hoveredItem(nullptr);
3611 	}
3612 	if (App::pressedItem() == view) {
3613 		App::pressedItem(nullptr);
3614 	}
3615 	if (App::hoveredLinkItem() == view) {
3616 		App::hoveredLinkItem(nullptr);
3617 	}
3618 	if (App::pressedLinkItem() == view) {
3619 		App::pressedLinkItem(nullptr);
3620 	}
3621 	if (App::mousedItem() == view) {
3622 		App::mousedItem(nullptr);
3623 	}
3624 }
3625 
folder(FolderId id)3626 not_null<Folder*> Session::folder(FolderId id) {
3627 	if (const auto result = folderLoaded(id)) {
3628 		return result;
3629 	}
3630 	const auto [it, ok] = _folders.emplace(
3631 		id,
3632 		std::make_unique<Folder>(this, id));
3633 	return it->second.get();
3634 }
3635 
folderLoaded(FolderId id) const3636 Folder *Session::folderLoaded(FolderId id) const {
3637 	const auto it = _folders.find(id);
3638 	return (it == end(_folders)) ? nullptr : it->second.get();
3639 }
3640 
processFolder(const MTPFolder & data)3641 not_null<Folder*> Session::processFolder(const MTPFolder &data) {
3642 	return data.match([&](const MTPDfolder &data) {
3643 		return processFolder(data);
3644 	});
3645 }
3646 
processFolder(const MTPDfolder & data)3647 not_null<Folder*> Session::processFolder(const MTPDfolder &data) {
3648 	return folder(data.vid().v);
3649 }
3650 
chatsList(Data::Folder * folder)3651 not_null<Dialogs::MainList*> Session::chatsList(Data::Folder *folder) {
3652 	return folder ? folder->chatsList().get() : &_chatsList;
3653 }
3654 
chatsList(Data::Folder * folder) const3655 not_null<const Dialogs::MainList*> Session::chatsList(
3656 		Data::Folder *folder) const {
3657 	return folder ? folder->chatsList() : &_chatsList;
3658 }
3659 
contactsList()3660 not_null<Dialogs::IndexedList*> Session::contactsList() {
3661 	return &_contactsList;
3662 }
3663 
contactsNoChatsList()3664 not_null<Dialogs::IndexedList*> Session::contactsNoChatsList() {
3665 	return &_contactsNoChatsList;
3666 }
3667 
refreshChatListEntry(Dialogs::Key key)3668 void Session::refreshChatListEntry(Dialogs::Key key) {
3669 	Expects(key.entry()->folderKnown());
3670 
3671 	using namespace Dialogs;
3672 
3673 	const auto entry = key.entry();
3674 	const auto history = key.history();
3675 	const auto mainList = chatsList(entry->folder());
3676 	auto event = ChatListEntryRefresh{ .key = key };
3677 	const auto creating = event.existenceChanged = !entry->inChatList();
3678 	if (event.existenceChanged) {
3679 		const auto mainRow = entry->addToChatList(0, mainList);
3680 		_contactsNoChatsList.del(key, mainRow);
3681 	} else {
3682 		event.moved = entry->adjustByPosInChatList(0, mainList);
3683 	}
3684 	if (event) {
3685 		_chatListEntryRefreshes.fire(std::move(event));
3686 	}
3687 	if (!history) {
3688 		return;
3689 	}
3690 	for (const auto &filter : _chatsFilters->list()) {
3691 		const auto id = filter.id();
3692 		const auto filterList = chatsFilters().chatsList(id);
3693 		auto event = ChatListEntryRefresh{ .key = key, .filterId = id };
3694 		if (filter.contains(history)) {
3695 			event.existenceChanged = !entry->inChatList(id);
3696 			if (event.existenceChanged) {
3697 				entry->addToChatList(id, filterList);
3698 			} else {
3699 				event.moved = entry->adjustByPosInChatList(id, filterList);
3700 			}
3701 		} else if (entry->inChatList(id)) {
3702 			entry->removeFromChatList(id, filterList);
3703 			event.existenceChanged = true;
3704 		}
3705 		if (event) {
3706 			_chatListEntryRefreshes.fire(std::move(event));
3707 		}
3708 	}
3709 
3710 	if (creating) {
3711 		if (const auto from = history->peer->migrateFrom()) {
3712 			if (const auto migrated = historyLoaded(from)) {
3713 				removeChatListEntry(migrated);
3714 			}
3715 		}
3716 	}
3717 }
3718 
removeChatListEntry(Dialogs::Key key)3719 void Session::removeChatListEntry(Dialogs::Key key) {
3720 	using namespace Dialogs;
3721 
3722 	const auto entry = key.entry();
3723 	if (!entry->inChatList()) {
3724 		return;
3725 	}
3726 	Assert(entry->folderKnown());
3727 	for (const auto &filter : _chatsFilters->list()) {
3728 		const auto id = filter.id();
3729 		if (entry->inChatList(id)) {
3730 			entry->removeFromChatList(id, chatsFilters().chatsList(id));
3731 			_chatListEntryRefreshes.fire(ChatListEntryRefresh{
3732 				.key = key,
3733 				.filterId = id,
3734 				.existenceChanged = true
3735 			});
3736 		}
3737 	}
3738 	const auto mainList = chatsList(entry->folder());
3739 	entry->removeFromChatList(0, mainList);
3740 	_chatListEntryRefreshes.fire(ChatListEntryRefresh{
3741 		.key = key,
3742 		.existenceChanged = true
3743 	});
3744 	if (_contactsList.contains(key)) {
3745 		if (!_contactsNoChatsList.contains(key)) {
3746 			_contactsNoChatsList.addByName(key);
3747 		}
3748 	}
3749 	if (const auto history = key.history()) {
3750 		Core::App().notifications().clearFromHistory(history);
3751 	}
3752 }
3753 
chatListEntryRefreshes() const3754 auto Session::chatListEntryRefreshes() const
3755 -> rpl::producer<ChatListEntryRefresh> {
3756 	return _chatListEntryRefreshes.events();
3757 }
3758 
3759 
dialogsRowReplaced(DialogsRowReplacement replacement)3760 void Session::dialogsRowReplaced(DialogsRowReplacement replacement) {
3761 	_dialogsRowReplacements.fire(std::move(replacement));
3762 }
3763 
dialogsRowReplacements() const3764 auto Session::dialogsRowReplacements() const
3765 -> rpl::producer<DialogsRowReplacement> {
3766 	return _dialogsRowReplacements.events();
3767 }
3768 
requestNotifySettings(not_null<PeerData * > peer)3769 void Session::requestNotifySettings(not_null<PeerData*> peer) {
3770 	if (peer->notifySettingsUnknown()) {
3771 		_session->api().requestNotifySettings(
3772 			MTP_inputNotifyPeer(peer->input));
3773 	}
3774 	if (defaultNotifySettings(peer).settingsUnknown()) {
3775 		_session->api().requestNotifySettings(peer->isUser()
3776 			? MTP_inputNotifyUsers()
3777 			: (peer->isChat() || peer->isMegagroup())
3778 			? MTP_inputNotifyChats()
3779 			: MTP_inputNotifyBroadcasts());
3780 	}
3781 }
3782 
applyNotifySetting(const MTPNotifyPeer & notifyPeer,const MTPPeerNotifySettings & settings)3783 void Session::applyNotifySetting(
3784 		const MTPNotifyPeer &notifyPeer,
3785 		const MTPPeerNotifySettings &settings) {
3786 	switch (notifyPeer.type()) {
3787 	case mtpc_notifyUsers: {
3788 		if (_defaultUserNotifySettings.change(settings)) {
3789 			_defaultUserNotifyUpdates.fire({});
3790 
3791 			enumerateUsers([&](not_null<UserData*> user) {
3792 				if (!user->notifySettingsUnknown()
3793 					&& ((!user->notifyMuteUntil()
3794 						&& _defaultUserNotifySettings.muteUntil())
3795 						|| (!user->notifySilentPosts()
3796 							&& _defaultUserNotifySettings.silentPosts()))) {
3797 					updateNotifySettingsLocal(user);
3798 				}
3799 			});
3800 		}
3801 	} break;
3802 	case mtpc_notifyChats: {
3803 		if (_defaultChatNotifySettings.change(settings)) {
3804 			_defaultChatNotifyUpdates.fire({});
3805 
3806 			enumerateGroups([&](not_null<PeerData*> peer) {
3807 				if (!peer->notifySettingsUnknown()
3808 					&& ((!peer->notifyMuteUntil()
3809 						&& _defaultChatNotifySettings.muteUntil())
3810 						|| (!peer->notifySilentPosts()
3811 							&& _defaultChatNotifySettings.silentPosts()))) {
3812 					updateNotifySettingsLocal(peer);
3813 				}
3814 			});
3815 		}
3816 	} break;
3817 	case mtpc_notifyBroadcasts: {
3818 		if (_defaultBroadcastNotifySettings.change(settings)) {
3819 			_defaultBroadcastNotifyUpdates.fire({});
3820 
3821 			enumerateChannels([&](not_null<ChannelData*> channel) {
3822 				if (!channel->notifySettingsUnknown()
3823 					&& ((!channel->notifyMuteUntil()
3824 						&& _defaultBroadcastNotifySettings.muteUntil())
3825 						|| (!channel->notifySilentPosts()
3826 							&& _defaultBroadcastNotifySettings.silentPosts()))) {
3827 					updateNotifySettingsLocal(channel);
3828 				}
3829 			});
3830 		}
3831 	} break;
3832 	case mtpc_notifyPeer: {
3833 		const auto &data = notifyPeer.c_notifyPeer();
3834 		if (const auto peer = peerLoaded(peerFromMTP(data.vpeer()))) {
3835 			if (peer->notifyChange(settings)) {
3836 				updateNotifySettingsLocal(peer);
3837 			}
3838 		}
3839 	} break;
3840 	}
3841 }
3842 
updateNotifySettings(not_null<PeerData * > peer,std::optional<int> muteForSeconds,std::optional<bool> silentPosts)3843 void Session::updateNotifySettings(
3844 		not_null<PeerData*> peer,
3845 		std::optional<int> muteForSeconds,
3846 		std::optional<bool> silentPosts) {
3847 	if (peer->notifyChange(muteForSeconds, silentPosts)) {
3848 		updateNotifySettingsLocal(peer);
3849 		_session->api().updateNotifySettingsDelayed(peer);
3850 	}
3851 }
3852 
resetNotifySettingsToDefault(not_null<PeerData * > peer)3853 void Session::resetNotifySettingsToDefault(not_null<PeerData*> peer) {
3854 	const auto empty = MTP_peerNotifySettings(
3855 		MTP_flags(0),
3856 		MTPBool(),
3857 		MTPBool(),
3858 		MTPint(),
3859 		MTPstring());
3860 	if (peer->notifyChange(empty)) {
3861 		updateNotifySettingsLocal(peer);
3862 		_session->api().updateNotifySettingsDelayed(peer);
3863 	}
3864 }
3865 
notifyIsMuted(not_null<const PeerData * > peer,crl::time * changesIn) const3866 bool Session::notifyIsMuted(
3867 		not_null<const PeerData*> peer,
3868 		crl::time *changesIn) const {
3869 	const auto resultFromUntil = [&](TimeId until) {
3870 		const auto now = base::unixtime::now();
3871 		const auto result = (until > now) ? (until - now) : 0;
3872 		if (changesIn) {
3873 			*changesIn = (result > 0)
3874 				? std::min(result * crl::time(1000), kMaxNotifyCheckDelay)
3875 				: kMaxNotifyCheckDelay;
3876 		}
3877 		return (result > 0);
3878 	};
3879 	if (const auto until = peer->notifyMuteUntil()) {
3880 		return resultFromUntil(*until);
3881 	}
3882 	const auto &settings = defaultNotifySettings(peer);
3883 	if (const auto until = settings.muteUntil()) {
3884 		return resultFromUntil(*until);
3885 	}
3886 	return true;
3887 }
3888 
notifySilentPosts(not_null<const PeerData * > peer) const3889 bool Session::notifySilentPosts(not_null<const PeerData*> peer) const {
3890 	if (const auto silent = peer->notifySilentPosts()) {
3891 		return *silent;
3892 	}
3893 	const auto &settings = defaultNotifySettings(peer);
3894 	if (const auto silent = settings.silentPosts()) {
3895 		return *silent;
3896 	}
3897 	return false;
3898 }
3899 
notifyMuteUnknown(not_null<const PeerData * > peer) const3900 bool Session::notifyMuteUnknown(not_null<const PeerData*> peer) const {
3901 	if (peer->notifySettingsUnknown()) {
3902 		return true;
3903 	} else if (const auto nonDefault = peer->notifyMuteUntil()) {
3904 		return false;
3905 	}
3906 	return defaultNotifySettings(peer).settingsUnknown();
3907 }
3908 
notifySilentPostsUnknown(not_null<const PeerData * > peer) const3909 bool Session::notifySilentPostsUnknown(
3910 		not_null<const PeerData*> peer) const {
3911 	if (peer->notifySettingsUnknown()) {
3912 		return true;
3913 	} else if (const auto nonDefault = peer->notifySilentPosts()) {
3914 		return false;
3915 	}
3916 	return defaultNotifySettings(peer).settingsUnknown();
3917 }
3918 
notifySettingsUnknown(not_null<const PeerData * > peer) const3919 bool Session::notifySettingsUnknown(not_null<const PeerData*> peer) const {
3920 	return notifyMuteUnknown(peer) || notifySilentPostsUnknown(peer);
3921 }
3922 
defaultUserNotifyUpdates() const3923 rpl::producer<> Session::defaultUserNotifyUpdates() const {
3924 	return _defaultUserNotifyUpdates.events();
3925 }
3926 
defaultChatNotifyUpdates() const3927 rpl::producer<> Session::defaultChatNotifyUpdates() const {
3928 	return _defaultChatNotifyUpdates.events();
3929 }
3930 
defaultBroadcastNotifyUpdates() const3931 rpl::producer<> Session::defaultBroadcastNotifyUpdates() const {
3932 	return _defaultBroadcastNotifyUpdates.events();
3933 }
3934 
defaultNotifyUpdates(not_null<const PeerData * > peer) const3935 rpl::producer<> Session::defaultNotifyUpdates(
3936 		not_null<const PeerData*> peer) const {
3937 	return peer->isUser()
3938 		? defaultUserNotifyUpdates()
3939 		: (peer->isChat() || peer->isMegagroup())
3940 		? defaultChatNotifyUpdates()
3941 		: defaultBroadcastNotifyUpdates();
3942 }
3943 
serviceNotification(const TextWithEntities & message,const MTPMessageMedia & media)3944 void Session::serviceNotification(
3945 		const TextWithEntities &message,
3946 		const MTPMessageMedia &media) {
3947 	const auto date = base::unixtime::now();
3948 	if (!peerLoaded(PeerData::kServiceNotificationsId)) {
3949 		processUser(MTP_user(
3950 			MTP_flags(
3951 				MTPDuser::Flag::f_first_name
3952 				| MTPDuser::Flag::f_phone
3953 				| MTPDuser::Flag::f_status
3954 				| MTPDuser::Flag::f_verified),
3955 			MTP_long(peerToUser(PeerData::kServiceNotificationsId).bare),
3956 			MTPlong(), // access_hash
3957 			MTP_string("Telegram"),
3958 			MTPstring(), // last_name
3959 			MTPstring(), // username
3960 			MTP_string("42777"),
3961 			MTP_userProfilePhotoEmpty(),
3962 			MTP_userStatusRecently(),
3963 			MTPint(), // bot_info_version
3964 			MTPVector<MTPRestrictionReason>(),
3965 			MTPstring(), // bot_inline_placeholder
3966 			MTPstring())); // lang_code
3967 	}
3968 	const auto history = this->history(PeerData::kServiceNotificationsId);
3969 	if (!history->folderKnown()) {
3970 		histories().requestDialogEntry(history, [=] {
3971 			insertCheckedServiceNotification(message, media, date);
3972 		});
3973 	} else {
3974 		insertCheckedServiceNotification(message, media, date);
3975 	}
3976 }
3977 
insertCheckedServiceNotification(const TextWithEntities & message,const MTPMessageMedia & media,TimeId date)3978 void Session::insertCheckedServiceNotification(
3979 		const TextWithEntities &message,
3980 		const MTPMessageMedia &media,
3981 		TimeId date) {
3982 	const auto flags = MTPDmessage::Flag::f_entities
3983 		| MTPDmessage::Flag::f_from_id
3984 		| MTPDmessage::Flag::f_media;
3985 	const auto localFlags = MessageFlag::ClientSideUnread
3986 		| MessageFlag::Local;
3987 	auto sending = TextWithEntities(), left = message;
3988 	while (TextUtilities::CutPart(sending, left, MaxMessageSize)) {
3989 		const auto id = nextLocalMessageId();
3990 		addNewMessage(
3991 			id,
3992 			MTP_message(
3993 				MTP_flags(flags),
3994 				MTP_int(0), // Not used (would've been trimmed to 32 bits).
3995 				peerToMTP(PeerData::kServiceNotificationsId),
3996 				peerToMTP(PeerData::kServiceNotificationsId),
3997 				MTPMessageFwdHeader(),
3998 				MTPlong(), // via_bot_id
3999 				MTPMessageReplyHeader(),
4000 				MTP_int(date),
4001 				MTP_string(sending.text),
4002 				media,
4003 				MTPReplyMarkup(),
4004 				Api::EntitiesToMTP(&session(), sending.entities),
4005 				MTPint(), // views
4006 				MTPint(), // forwards
4007 				MTPMessageReplies(),
4008 				MTPint(), // edit_date
4009 				MTPstring(),
4010 				MTPlong(),
4011 				//MTPMessageReactions(),
4012 				MTPVector<MTPRestrictionReason>(),
4013 				MTPint()), // ttl_period
4014 			localFlags,
4015 			NewMessageType::Unread);
4016 	}
4017 	sendHistoryChangeNotifications();
4018 }
4019 
setMimeForwardIds(MessageIdsList && list)4020 void Session::setMimeForwardIds(MessageIdsList &&list) {
4021 	_mimeForwardIds = std::move(list);
4022 }
4023 
takeMimeForwardIds()4024 MessageIdsList Session::takeMimeForwardIds() {
4025 	return std::move(_mimeForwardIds);
4026 }
4027 
setTopPromoted(History * promoted,const QString & type,const QString & message)4028 void Session::setTopPromoted(
4029 		History *promoted,
4030 		const QString &type,
4031 		const QString &message) {
4032 	const auto changed = (_topPromoted != promoted);
4033 	if (!changed
4034 		&& (!promoted || promoted->topPromotionMessage() == message)) {
4035 		return;
4036 	}
4037 	if (changed) {
4038 		if (_topPromoted) {
4039 			_topPromoted->cacheTopPromotion(false, QString(), QString());
4040 		}
4041 	}
4042 	const auto old = std::exchange(_topPromoted, promoted);
4043 	if (_topPromoted) {
4044 		histories().requestDialogEntry(_topPromoted);
4045 		_topPromoted->cacheTopPromotion(true, type, message);
4046 		_topPromoted->requestChatListMessage();
4047 		session().changes().historyUpdated(
4048 			_topPromoted,
4049 			HistoryUpdate::Flag::TopPromoted);
4050 	}
4051 	if (changed && old) {
4052 		session().changes().historyUpdated(
4053 			old,
4054 			HistoryUpdate::Flag::TopPromoted);
4055 	}
4056 }
4057 
updateWallpapers(const MTPaccount_WallPapers & data)4058 bool Session::updateWallpapers(const MTPaccount_WallPapers &data) {
4059 	return data.match([&](const MTPDaccount_wallPapers &data) {
4060 		setWallpapers(data.vwallpapers().v, data.vhash().v);
4061 		return true;
4062 	}, [&](const MTPDaccount_wallPapersNotModified &) {
4063 		return false;
4064 	});
4065 }
4066 
setWallpapers(const QVector<MTPWallPaper> & data,uint64 hash)4067 void Session::setWallpapers(const QVector<MTPWallPaper> &data, uint64 hash) {
4068 	_wallpapersHash = hash;
4069 
4070 	_wallpapers.clear();
4071 	_wallpapers.reserve(data.size() + 2);
4072 
4073 	_wallpapers.push_back(Data::Legacy1DefaultWallPaper());
4074 	_wallpapers.back().setLocalImageAsThumbnail(std::make_shared<Image>(
4075 		u":/gui/art/bg_initial.jpg"_q));
4076 	for (const auto &paper : data) {
4077 		if (const auto parsed = Data::WallPaper::Create(&session(), paper)) {
4078 			_wallpapers.push_back(*parsed);
4079 		}
4080 	}
4081 
4082 	// Put the legacy2 (flowers) wallpaper to the front of the list.
4083 	const auto legacy2 = ranges::find_if(
4084 		_wallpapers,
4085 		Data::IsLegacy2DefaultWallPaper);
4086 	if (legacy2 != end(_wallpapers)) {
4087 		ranges::rotate(begin(_wallpapers), legacy2, legacy2 + 1);
4088 	}
4089 
4090 	// Put the legacy3 (static gradient) wallpaper to the front of the list.
4091 	const auto legacy3 = ranges::find_if(
4092 		_wallpapers,
4093 		Data::IsLegacy3DefaultWallPaper);
4094 	if (legacy3 != end(_wallpapers)) {
4095 		ranges::rotate(begin(_wallpapers), legacy3, legacy3 + 1);
4096 	}
4097 
4098 	if (ranges::none_of(_wallpapers, Data::IsDefaultWallPaper)) {
4099 		_wallpapers.push_back(Data::DefaultWallPaper());
4100 		_wallpapers.back().setLocalImageAsThumbnail(std::make_shared<Image>(
4101 			u":/gui/art/bg_thumbnail.png"_q));
4102 	}
4103 }
4104 
removeWallpaper(const WallPaper & paper)4105 void Session::removeWallpaper(const WallPaper &paper) {
4106 	const auto i = ranges::find(_wallpapers, paper.id(), &WallPaper::id);
4107 	if (i != end(_wallpapers)) {
4108 		_wallpapers.erase(i);
4109 	}
4110 }
4111 
wallpapers() const4112 const std::vector<WallPaper> &Session::wallpapers() const {
4113 	return _wallpapers;
4114 }
4115 
wallpapersHash() const4116 uint64 Session::wallpapersHash() const {
4117 	return _wallpapersHash;
4118 }
4119 
clearLocalStorage()4120 void Session::clearLocalStorage() {
4121 	_cache->close();
4122 	_cache->clear();
4123 	_bigFileCache->close();
4124 	_bigFileCache->clear();
4125 }
4126 
4127 } // namespace Data
4128