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 "storage/localstorage.h"
9 //
10 #include "storage/serialize_common.h"
11 #include "storage/storage_account.h"
12 #include "storage/details/storage_file_utilities.h"
13 #include "storage/details/storage_settings_scheme.h"
14 #include "data/data_session.h"
15 #include "data/data_document.h"
16 #include "data/data_document_media.h"
17 #include "base/platform/base_platform_info.h"
18 #include "base/random.h"
19 #include "ui/effects/animation_value.h"
20 #include "core/update_checker.h"
21 #include "core/file_location.h"
22 #include "core/application.h"
23 #include "media/audio/media_audio.h"
24 #include "mtproto/mtproto_config.h"
25 #include "mtproto/mtproto_dc_options.h"
26 #include "main/main_domain.h"
27 #include "main/main_account.h"
28 #include "main/main_session.h"
29 #include "window/themes/window_theme.h"
30 #include "lang/lang_instance.h"
31 
32 #include <QtCore/QDirIterator>
33 
34 #ifndef Q_OS_WIN
35 #include <unistd.h>
36 #endif // Q_OS_WIN
37 
38 //extern "C" {
39 //#include <openssl/evp.h>
40 //} // extern "C"
41 
42 namespace Local {
43 namespace {
44 
45 constexpr auto kThemeFileSizeLimit = 5 * 1024 * 1024;
46 constexpr auto kFileLoaderQueueStopTimeout = crl::time(5000);
47 
48 constexpr auto kSavedBackgroundFormat = QImage::Format_ARGB32_Premultiplied;
49 constexpr auto kWallPaperLegacySerializeTagId = int32(-111);
50 constexpr auto kWallPaperSerializeTagId = int32(-112);
51 constexpr auto kWallPaperSidesLimit = 10'000;
52 
53 const auto kThemeNewPathRelativeTag = qstr("special://new_tag");
54 
55 using namespace Storage::details;
56 using Storage::FileKey;
57 
58 using Database = Storage::Cache::Database;
59 
60 QString _basePath, _userBasePath, _userDbPath;
61 
62 TaskQueue *_localLoader = nullptr;
63 
64 QByteArray _settingsSalt;
65 
66 auto OldKey = MTP::AuthKeyPtr();
67 auto SettingsKey = MTP::AuthKeyPtr();
68 
69 FileKey _themeKeyDay = 0;
70 FileKey _themeKeyNight = 0;
71 
72 // Theme key legacy may be read in start() with settings.
73 // But it should be moved to keyDay or keyNight inside InitialLoadTheme()
74 // and never used after.
75 FileKey _themeKeyLegacy = 0;
76 FileKey _langPackKey = 0;
77 FileKey _languagesKey = 0;
78 
79 FileKey _backgroundKeyDay = 0;
80 FileKey _backgroundKeyNight = 0;
81 bool _useGlobalBackgroundKeys = false;
82 bool _backgroundCanWrite = true;
83 
84 int32 _oldSettingsVersion = 0;
85 bool _settingsRewriteNeeded = false;
86 bool _settingsWriteAllowed = false;
87 
88 enum class WriteMapWhen {
89 	Now,
90 	Fast,
91 	Soon,
92 };
93 
CheckStreamStatus(QDataStream & stream)94 bool CheckStreamStatus(QDataStream &stream) {
95 	if (stream.status() != QDataStream::Ok) {
96 		LOG(("Bad data stream status: %1").arg(stream.status()));
97 		return false;
98 	}
99 	return true;
100 }
101 
LookupFallbackConfig()102 [[nodiscard]] const MTP::Config &LookupFallbackConfig() {
103 	static const auto lookupConfig = [](not_null<Main::Account*> account) {
104 		const auto mtp = &account->mtp();
105 		const auto production = MTP::Environment::Production;
106 		return (mtp->environment() == production)
107 			? &mtp->config()
108 			: nullptr;
109 	};
110 	const auto &app = Core::App();
111 	const auto &domain = app.domain();
112 	if (!domain.started()) {
113 		return app.fallbackProductionConfig();
114 	}
115 	if (const auto result = lookupConfig(&app.activeAccount())) {
116 		return *result;
117 	}
118 	for (const auto &[_, account] : domain.accounts()) {
119 		if (const auto result = lookupConfig(account.get())) {
120 			return *result;
121 		}
122 	}
123 	return app.fallbackProductionConfig();
124 }
125 
applyReadContext(ReadSettingsContext && context)126 void applyReadContext(ReadSettingsContext &&context) {
127 	ApplyReadFallbackConfig(context);
128 
129 	DEBUG_LOG(("Theme: applying context, legacy: %1, day: %2, night: %3"
130 		).arg(context.themeKeyLegacy
131 		).arg(context.themeKeyDay
132 		).arg(context.themeKeyNight));
133 	_themeKeyLegacy = context.themeKeyLegacy;
134 	_themeKeyDay = context.themeKeyDay;
135 	_themeKeyNight = context.themeKeyNight;
136 	_backgroundKeyDay = context.backgroundKeyDay;
137 	_backgroundKeyNight = context.backgroundKeyNight;
138 	_useGlobalBackgroundKeys = context.backgroundKeysRead;
139 	_langPackKey = context.langPackKey;
140 	_languagesKey = context.languagesKey;
141 }
142 
_readOldSettings(bool remove,ReadSettingsContext & context)143 bool _readOldSettings(bool remove, ReadSettingsContext &context) {
144 	bool result = false;
145 	QFile file(cWorkingDir() + qsl("tdata/config"));
146 	if (file.open(QIODevice::ReadOnly)) {
147 		LOG(("App Info: reading old config..."));
148 		QDataStream stream(&file);
149 		stream.setVersion(QDataStream::Qt_5_1);
150 
151 		qint32 version = 0;
152 		while (!stream.atEnd()) {
153 			quint32 blockId;
154 			stream >> blockId;
155 			if (!CheckStreamStatus(stream)) break;
156 
157 			if (blockId == dbiVersion) {
158 				stream >> version;
159 				if (!CheckStreamStatus(stream)) break;
160 
161 				if (version > AppVersion) break;
162 			} else if (!ReadSetting(blockId, stream, version, context)) {
163 				break;
164 			}
165 		}
166 		file.close();
167 		result = true;
168 	}
169 	if (remove) file.remove();
170 	return result;
171 }
172 
_readOldUserSettingsFields(QIODevice * device,qint32 & version,ReadSettingsContext & context)173 void _readOldUserSettingsFields(
174 		QIODevice *device,
175 		qint32 &version,
176 		ReadSettingsContext &context) {
177 	QDataStream stream(device);
178 	stream.setVersion(QDataStream::Qt_5_1);
179 
180 	while (!stream.atEnd()) {
181 		quint32 blockId;
182 		stream >> blockId;
183 		if (!CheckStreamStatus(stream)) {
184 			break;
185 		}
186 
187 		if (blockId == dbiVersion) {
188 			stream >> version;
189 			if (!CheckStreamStatus(stream)) {
190 				break;
191 			}
192 
193 			if (version > AppVersion) return;
194 		} else if (blockId == dbiEncryptedWithSalt) {
195 			QByteArray salt, data, decrypted;
196 			stream >> salt >> data;
197 			if (!CheckStreamStatus(stream)) {
198 				break;
199 			}
200 
201 			if (salt.size() != 32) {
202 				LOG(("App Error: bad salt in old user config encrypted part, size: %1").arg(salt.size()));
203 				continue;
204 			}
205 
206 			OldKey = CreateLegacyLocalKey(QByteArray(), salt);
207 
208 			if (data.size() <= 16 || (data.size() & 0x0F)) {
209 				LOG(("App Error: bad encrypted part size in old user config: %1").arg(data.size()));
210 				continue;
211 			}
212 			uint32 fullDataLen = data.size() - 16;
213 			decrypted.resize(fullDataLen);
214 			const char *dataKey = data.constData(), *encrypted = data.constData() + 16;
215 			aesDecryptLocal(encrypted, decrypted.data(), fullDataLen, OldKey, dataKey);
216 			uchar sha1Buffer[20];
217 			if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), dataKey, 16)) {
218 				LOG(("App Error: bad decrypt key, data from old user config not decrypted"));
219 				continue;
220 			}
221 			uint32 dataLen = *(const uint32*)decrypted.constData();
222 			if (dataLen > uint32(decrypted.size()) || dataLen <= fullDataLen - 16 || dataLen < 4) {
223 				LOG(("App Error: bad decrypted part size in old user config: %1, fullDataLen: %2, decrypted size: %3").arg(dataLen).arg(fullDataLen).arg(decrypted.size()));
224 				continue;
225 			}
226 			decrypted.resize(dataLen);
227 			QBuffer decryptedStream(&decrypted);
228 			decryptedStream.open(QIODevice::ReadOnly);
229 			decryptedStream.seek(4); // skip size
230 			LOG(("App Info: reading encrypted old user config..."));
231 
232 			_readOldUserSettingsFields(&decryptedStream, version, context);
233 		} else if (!ReadSetting(blockId, stream, version, context)) {
234 			return;
235 		}
236 	}
237 }
238 
_readOldUserSettings(bool remove,ReadSettingsContext & context)239 bool _readOldUserSettings(bool remove, ReadSettingsContext &context) {
240 	bool result = false;
241 	// We dropped old test authorizations when migrated to multi auth.
242 	//const auto testPrefix = (cTestMode() ? qsl("_test") : QString());
243 	const auto testPrefix = QString();
244 	QFile file(cWorkingDir() + cDataFile() + testPrefix + qsl("_config"));
245 	if (file.open(QIODevice::ReadOnly)) {
246 		LOG(("App Info: reading old user config..."));
247 		qint32 version = 0;
248 		_readOldUserSettingsFields(&file, version, context);
249 		file.close();
250 		result = true;
251 	}
252 	if (remove) file.remove();
253 	return result;
254 }
255 
_readOldMtpDataFields(QIODevice * device,qint32 & version,ReadSettingsContext & context)256 void _readOldMtpDataFields(
257 		QIODevice *device,
258 		qint32 &version,
259 		ReadSettingsContext &context) {
260 	QDataStream stream(device);
261 	stream.setVersion(QDataStream::Qt_5_1);
262 
263 	while (!stream.atEnd()) {
264 		quint32 blockId;
265 		stream >> blockId;
266 		if (!CheckStreamStatus(stream)) {
267 			break;
268 		}
269 
270 		if (blockId == dbiVersion) {
271 			stream >> version;
272 			if (!CheckStreamStatus(stream)) {
273 				break;
274 			}
275 
276 			if (version > AppVersion) return;
277 		} else if (blockId == dbiEncrypted) {
278 			QByteArray data, decrypted;
279 			stream >> data;
280 			if (!CheckStreamStatus(stream)) {
281 				break;
282 			}
283 
284 			if (!OldKey) {
285 				LOG(("MTP Error: reading old encrypted keys without old key!"));
286 				continue;
287 			}
288 
289 			if (data.size() <= 16 || (data.size() & 0x0F)) {
290 				LOG(("MTP Error: bad encrypted part size in old keys: %1").arg(data.size()));
291 				continue;
292 			}
293 			uint32 fullDataLen = data.size() - 16;
294 			decrypted.resize(fullDataLen);
295 			const char *dataKey = data.constData(), *encrypted = data.constData() + 16;
296 			aesDecryptLocal(encrypted, decrypted.data(), fullDataLen, OldKey, dataKey);
297 			uchar sha1Buffer[20];
298 			if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), dataKey, 16)) {
299 				LOG(("MTP Error: bad decrypt key, data from old keys not decrypted"));
300 				continue;
301 			}
302 			uint32 dataLen = *(const uint32*)decrypted.constData();
303 			if (dataLen > uint32(decrypted.size()) || dataLen <= fullDataLen - 16 || dataLen < 4) {
304 				LOG(("MTP Error: bad decrypted part size in old keys: %1, fullDataLen: %2, decrypted size: %3").arg(dataLen).arg(fullDataLen).arg(decrypted.size()));
305 				continue;
306 			}
307 			decrypted.resize(dataLen);
308 			QBuffer decryptedStream(&decrypted);
309 			decryptedStream.open(QIODevice::ReadOnly);
310 			decryptedStream.seek(4); // skip size
311 			LOG(("App Info: reading encrypted old keys..."));
312 
313 			_readOldMtpDataFields(&decryptedStream, version, context);
314 		} else if (!ReadSetting(blockId, stream, version, context)) {
315 			return;
316 		}
317 	}
318 }
319 
_readOldMtpData(bool remove,ReadSettingsContext & context)320 bool _readOldMtpData(bool remove, ReadSettingsContext &context) {
321 	bool result = false;
322 	// We dropped old test authorizations when migrated to multi auth.
323 	//const auto testPostfix = (cTestMode() ? qsl("_test") : QString());
324 	const auto testPostfix = QString();
325 	QFile file(cWorkingDir() + cDataFile() + testPostfix);
326 	if (file.open(QIODevice::ReadOnly)) {
327 		LOG(("App Info: reading old keys..."));
328 		qint32 version = 0;
329 		_readOldMtpDataFields(&file, version, context);
330 		file.close();
331 		result = true;
332 	}
333 	if (remove) file.remove();
334 	return result;
335 }
336 
337 } // namespace
338 
sync()339 void sync() {
340 	Storage::details::Sync();
341 }
342 
finish()343 void finish() {
344 	delete base::take(_localLoader);
345 	Storage::details::Finish();
346 }
347 
348 void InitialLoadTheme();
349 bool ApplyDefaultNightMode();
350 void readLangPack();
351 
start()352 void start() {
353 	Expects(_basePath.isEmpty());
354 
355 	_localLoader = new TaskQueue(kFileLoaderQueueStopTimeout);
356 
357 	_basePath = cWorkingDir() + qsl("tdata/");
358 	if (!QDir().exists(_basePath)) QDir().mkpath(_basePath);
359 
360 	ReadSettingsContext context;
361 	FileReadDescriptor settingsData;
362 	// We dropped old test authorizations when migrated to multi auth.
363 	//const auto name = cTestMode() ? qsl("settings_test") : qsl("settings");
364 	const auto name = u"settings"_q;
365 	if (!ReadFile(settingsData, name, _basePath)) {
366 		_readOldSettings(true, context);
367 		_readOldUserSettings(false, context); // needed further in _readUserSettings
368 		_readOldMtpData(false, context); // needed further in _readMtpData
369 		applyReadContext(std::move(context));
370 
371 		_settingsRewriteNeeded = true;
372 		ApplyDefaultNightMode();
373 		return;
374 	}
375 	LOG(("App Info: reading settings..."));
376 
377 	QByteArray salt, settingsEncrypted;
378 	settingsData.stream >> salt >> settingsEncrypted;
379 	if (!CheckStreamStatus(settingsData.stream)) {
380 		return writeSettings();
381 	}
382 
383 	if (salt.size() != LocalEncryptSaltSize) {
384 		LOG(("App Error: bad salt in settings file, size: %1").arg(salt.size()));
385 		return writeSettings();
386 	}
387 	SettingsKey = CreateLegacyLocalKey(QByteArray(), salt);
388 
389 	EncryptedDescriptor settings;
390 	if (!DecryptLocal(settings, settingsEncrypted, SettingsKey)) {
391 		LOG(("App Error: could not decrypt settings from settings file..."));
392 		return writeSettings();
393 	}
394 
395 	LOG(("App Info: reading encrypted settings..."));
396 	while (!settings.stream.atEnd()) {
397 		quint32 blockId;
398 		settings.stream >> blockId;
399 		if (!CheckStreamStatus(settings.stream)) {
400 			return writeSettings();
401 		}
402 
403 		if (!ReadSetting(blockId, settings.stream, settingsData.version, context)) {
404 			return writeSettings();
405 		}
406 	}
407 
408 	_oldSettingsVersion = settingsData.version;
409 	_settingsSalt = salt;
410 
411 	applyReadContext(std::move(context));
412 	if (context.legacyRead) {
413 		writeSettings();
414 	}
415 
416 	InitialLoadTheme();
417 
418 	if (context.tileRead && _useGlobalBackgroundKeys) {
419 		Window::Theme::Background()->setTileDayValue(context.tileDay);
420 		Window::Theme::Background()->setTileNightValue(context.tileNight);
421 	}
422 
423 	readLangPack();
424 }
425 
writeSettings()426 void writeSettings() {
427 	if (!_settingsWriteAllowed) {
428 		_settingsRewriteNeeded = true;
429 
430 		// We need to generate SettingsKey anyway,
431 		// for the moveLegacyBackground to work.
432 		if (SettingsKey) {
433 			return;
434 		}
435 	}
436 	if (_basePath.isEmpty()) {
437 		LOG(("App Error: _basePath is empty in writeSettings()"));
438 		return;
439 	}
440 
441 	if (!QDir().exists(_basePath)) QDir().mkpath(_basePath);
442 
443 	// We dropped old test authorizations when migrated to multi auth.
444 	//const auto name = cTestMode() ? qsl("settings_test") : qsl("settings");
445 	const auto name = u"settings"_q;
446 	FileWriteDescriptor settings(name, _basePath);
447 	if (_settingsSalt.isEmpty() || !SettingsKey) {
448 		_settingsSalt.resize(LocalEncryptSaltSize);
449 		base::RandomFill(_settingsSalt.data(), _settingsSalt.size());
450 		SettingsKey = CreateLegacyLocalKey(QByteArray(), _settingsSalt);
451 	}
452 	settings.writeData(_settingsSalt);
453 
454 	if (!_settingsWriteAllowed) {
455 		EncryptedDescriptor data(0);
456 		settings.writeEncrypted(data, SettingsKey);
457 		return;
458 	}
459 	const auto configSerialized = LookupFallbackConfig().serialize();
460 	const auto applicationSettings = Core::App().settings().serialize();
461 
462 	quint32 size = 9 * (sizeof(quint32) + sizeof(qint32));
463 	size += sizeof(quint32) + Serialize::bytearraySize(configSerialized);
464 	size += sizeof(quint32) + Serialize::bytearraySize(applicationSettings);
465 	size += sizeof(quint32) + Serialize::stringSize(cDialogLastPath());
466 
467 	// Theme keys and night mode.
468 	size += sizeof(quint32) + sizeof(quint64) * 2 + sizeof(quint32);
469 	size += sizeof(quint32) + sizeof(quint64) * 2;
470 	if (_langPackKey) {
471 		size += sizeof(quint32) + sizeof(quint64);
472 	}
473 	size += sizeof(quint32) + sizeof(qint32) * 8;
474 
475 	EncryptedDescriptor data(size);
476 	data.stream << quint32(dbiAutoStart) << qint32(cAutoStart());
477 	data.stream << quint32(dbiStartMinimized) << qint32(cStartMinimized());
478 	data.stream << quint32(dbiSendToMenu) << qint32(cSendToMenu());
479 	data.stream << quint32(dbiSeenTrayTooltip) << qint32(cSeenTrayTooltip());
480 	data.stream << quint32(dbiAutoUpdate) << qint32(cAutoUpdate());
481 	data.stream << quint32(dbiLastUpdateCheck) << qint32(cLastUpdateCheck());
482 	data.stream << quint32(dbiScalePercent) << qint32(cConfigScale());
483 	data.stream << quint32(dbiFallbackProductionConfig) << configSerialized;
484 	data.stream << quint32(dbiApplicationSettings) << applicationSettings;
485 	data.stream << quint32(dbiDialogLastPath) << cDialogLastPath();
486 	data.stream << quint32(dbiAnimationsDisabled) << qint32(anim::Disabled() ? 1 : 0);
487 
488 	data.stream
489 		<< quint32(dbiThemeKey)
490 		<< quint64(_themeKeyDay)
491 		<< quint64(_themeKeyNight)
492 		<< quint32(Window::Theme::IsNightMode() ? 1 : 0);
493 	if (_useGlobalBackgroundKeys) {
494 		data.stream
495 			<< quint32(dbiBackgroundKey)
496 			<< quint64(_backgroundKeyDay)
497 			<< quint64(_backgroundKeyNight);
498 		data.stream
499 			<< quint32(dbiTileBackground)
500 			<< qint32(Window::Theme::Background()->tileDay() ? 1 : 0)
501 			<< qint32(Window::Theme::Background()->tileNight() ? 1 : 0);
502 	}
503 	if (_langPackKey) {
504 		data.stream << quint32(dbiLangPackKey) << quint64(_langPackKey);
505 	}
506 	if (_languagesKey) {
507 		data.stream << quint32(dbiLanguagesKey) << quint64(_languagesKey);
508 	}
509 
510 	settings.writeEncrypted(data, SettingsKey);
511 }
512 
rewriteSettingsIfNeeded()513 void rewriteSettingsIfNeeded() {
514 	if (_settingsWriteAllowed) {
515 		return;
516 	}
517 	_settingsWriteAllowed = true;
518 	if (_oldSettingsVersion < AppVersion || _settingsRewriteNeeded) {
519 		writeSettings();
520 	}
521 }
522 
AutoupdatePrefix(const QString & replaceWith={})523 const QString &AutoupdatePrefix(const QString &replaceWith = {}) {
524 	Expects(!Core::UpdaterDisabled());
525 
526 	static auto value = QString();
527 	if (!replaceWith.isEmpty()) {
528 		value = replaceWith;
529 	}
530 	return value;
531 }
532 
autoupdatePrefixFile()533 QString autoupdatePrefixFile() {
534 	Expects(!Core::UpdaterDisabled());
535 
536 	return cWorkingDir() + "tdata/prefix";
537 }
538 
readAutoupdatePrefixRaw()539 const QString &readAutoupdatePrefixRaw() {
540 	Expects(!Core::UpdaterDisabled());
541 
542 	const auto &result = AutoupdatePrefix();
543 	if (!result.isEmpty()) {
544 		return result;
545 	}
546 	QFile f(autoupdatePrefixFile());
547 	if (f.open(QIODevice::ReadOnly)) {
548 		const auto value = QString::fromUtf8(f.readAll());
549 		if (!value.isEmpty()) {
550 			return AutoupdatePrefix(value);
551 		}
552 	}
553 	return AutoupdatePrefix("https://td.telegram.org");
554 }
555 
writeAutoupdatePrefix(const QString & prefix)556 void writeAutoupdatePrefix(const QString &prefix) {
557 	if (Core::UpdaterDisabled()) {
558 		return;
559 	}
560 
561 	const auto current = readAutoupdatePrefixRaw();
562 	if (current != prefix) {
563 		AutoupdatePrefix(prefix);
564 		QFile f(autoupdatePrefixFile());
565 		if (f.open(QIODevice::WriteOnly)) {
566 			f.write(prefix.toUtf8());
567 			f.close();
568 		}
569 		if (cAutoUpdate()) {
570 			Core::UpdateChecker checker;
571 			checker.start();
572 		}
573 	}
574 }
575 
readAutoupdatePrefix()576 QString readAutoupdatePrefix() {
577 	Expects(!Core::UpdaterDisabled());
578 
579 	auto result = readAutoupdatePrefixRaw();
580 	return result.replace(QRegularExpression("/+$"), QString());
581 }
582 
writeBackground(const Data::WallPaper & paper,const QImage & image)583 void writeBackground(const Data::WallPaper &paper, const QImage &image) {
584 	Expects(_settingsWriteAllowed);
585 
586 	if (!_backgroundCanWrite) {
587 		return;
588 	}
589 
590 	_useGlobalBackgroundKeys = true;
591 	auto &backgroundKey = Window::Theme::IsNightMode()
592 		? _backgroundKeyNight
593 		: _backgroundKeyDay;
594 	auto imageData = QByteArray();
595 	if (!image.isNull()) {
596 		const auto width = qint32(image.width());
597 		const auto height = qint32(image.height());
598 		const auto perpixel = (image.depth() >> 3);
599 		const auto srcperline = image.bytesPerLine();
600 		const auto srcsize = srcperline * height;
601 		const auto dstperline = width * perpixel;
602 		const auto dstsize = dstperline * height;
603 		const auto copy = (image.format() != kSavedBackgroundFormat)
604 			? image.convertToFormat(kSavedBackgroundFormat)
605 			: image;
606 		imageData.resize(2 * sizeof(qint32) + dstsize);
607 
608 		auto dst = bytes::make_detached_span(imageData);
609 		bytes::copy(dst, bytes::object_as_span(&width));
610 		dst = dst.subspan(sizeof(qint32));
611 		bytes::copy(dst, bytes::object_as_span(&height));
612 		dst = dst.subspan(sizeof(qint32));
613 		const auto src = bytes::make_span(image.constBits(), srcsize);
614 		if (srcsize == dstsize) {
615 			bytes::copy(dst, src);
616 		} else {
617 			for (auto y = 0; y != height; ++y) {
618 				bytes::copy(dst, src.subspan(y * srcperline, dstperline));
619 				dst = dst.subspan(dstperline);
620 			}
621 		}
622 	}
623 	if (!backgroundKey) {
624 		backgroundKey = GenerateKey(_basePath);
625 		writeSettings();
626 	}
627 	const auto serialized = paper.serialize();
628 	quint32 size = sizeof(qint32)
629 		+ Serialize::bytearraySize(serialized)
630 		+ Serialize::bytearraySize(imageData);
631 	EncryptedDescriptor data(size);
632 	data.stream
633 		<< qint32(kWallPaperSerializeTagId)
634 		<< serialized
635 		<< imageData;
636 
637 	FileWriteDescriptor file(backgroundKey, _basePath);
638 	file.writeEncrypted(data, SettingsKey);
639 }
640 
readBackground()641 bool readBackground() {
642 	FileReadDescriptor bg;
643 	auto &backgroundKey = Window::Theme::IsNightMode()
644 		? _backgroundKeyNight
645 		: _backgroundKeyDay;
646 	if (!ReadEncryptedFile(bg, backgroundKey, _basePath, SettingsKey)) {
647 		if (backgroundKey) {
648 			ClearKey(backgroundKey, _basePath);
649 			backgroundKey = 0;
650 			writeSettings();
651 		}
652 		return false;
653 	}
654 
655 	qint32 legacyId = 0;
656 	bg.stream >> legacyId;
657 	const auto paper = [&] {
658 		if (legacyId == kWallPaperLegacySerializeTagId) {
659 			quint64 id = 0;
660 			quint64 accessHash = 0;
661 			quint32 flags = 0;
662 			QString slug;
663 			bg.stream
664 				>> id
665 				>> accessHash
666 				>> flags
667 				>> slug;
668 			return Data::WallPaper::FromLegacySerialized(
669 				id,
670 				accessHash,
671 				flags,
672 				slug);
673 		} else if (legacyId == kWallPaperSerializeTagId) {
674 			QByteArray serialized;
675 			bg.stream >> serialized;
676 			return Data::WallPaper::FromSerialized(serialized);
677 		} else {
678 			return Data::WallPaper::FromLegacyId(legacyId);
679 		}
680 	}();
681 	if (bg.stream.status() != QDataStream::Ok || !paper) {
682 		return false;
683 	}
684 
685 	QByteArray imageData;
686 	bg.stream >> imageData;
687 	const auto isOldEmptyImage = (bg.stream.status() != QDataStream::Ok);
688 	if (isOldEmptyImage
689 		|| Data::IsLegacy1DefaultWallPaper(*paper)
690 		|| (Data::IsLegacy2DefaultWallPaper(*paper) && bg.version < 3000000)
691 		|| (Data::IsLegacy3DefaultWallPaper(*paper) && bg.version < 3000000)
692 		|| (Data::IsLegacy4DefaultWallPaper(*paper) && bg.version < 3000000)
693 		|| Data::IsDefaultWallPaper(*paper)) {
694 		_backgroundCanWrite = false;
695 		if (isOldEmptyImage || bg.version < 3000000) {
696 			Window::Theme::Background()->set(Data::DefaultWallPaper());
697 		} else {
698 			Window::Theme::Background()->set(*paper);
699 		}
700 		_backgroundCanWrite = true;
701 		return true;
702 	} else if (Data::IsThemeWallPaper(*paper) && imageData.isEmpty()) {
703 		_backgroundCanWrite = false;
704 		Window::Theme::Background()->set(*paper);
705 		_backgroundCanWrite = true;
706 		return true;
707 	}
708 	auto image = QImage();
709 	if (legacyId == kWallPaperSerializeTagId) {
710 		const auto perpixel = 4;
711 		auto src = bytes::make_span(imageData);
712 		auto width = qint32();
713 		auto height = qint32();
714 		if (src.size() > 2 * sizeof(qint32)) {
715 			bytes::copy(
716 				bytes::object_as_span(&width),
717 				src.subspan(0, sizeof(qint32)));
718 			src = src.subspan(sizeof(qint32));
719 			bytes::copy(
720 				bytes::object_as_span(&height),
721 				src.subspan(0, sizeof(qint32)));
722 			src = src.subspan(sizeof(qint32));
723 			if (width + height <= kWallPaperSidesLimit
724 				&& src.size() == width * height * perpixel) {
725 				image = QImage(
726 					width,
727 					height,
728 					QImage::Format_ARGB32_Premultiplied);
729 				if (!image.isNull()) {
730 					const auto srcperline = width * perpixel;
731 					const auto srcsize = srcperline * height;
732 					const auto dstperline = image.bytesPerLine();
733 					const auto dstsize = dstperline * height;
734 					Assert(srcsize == dstsize);
735 					bytes::copy(
736 						bytes::make_span(image.bits(), dstsize),
737 						src);
738 				}
739 			}
740 		}
741 	} else {
742 		auto buffer = QBuffer(&imageData);
743 		auto reader = QImageReader(&buffer);
744 		reader.setAutoTransform(true);
745 		if (!reader.read(&image)) {
746 			image = QImage();
747 		}
748 	}
749 	if (!image.isNull() || !paper->backgroundColors().empty()) {
750 		_backgroundCanWrite = false;
751 		Window::Theme::Background()->set(*paper, std::move(image));
752 		_backgroundCanWrite = true;
753 		return true;
754 	}
755 	return false;
756 }
757 
moveLegacyBackground(const QString & fromBasePath,const MTP::AuthKeyPtr & fromLocalKey,uint64 legacyBackgroundKeyDay,uint64 legacyBackgroundKeyNight)758 void moveLegacyBackground(
759 		const QString &fromBasePath,
760 		const MTP::AuthKeyPtr &fromLocalKey,
761 		uint64 legacyBackgroundKeyDay,
762 		uint64 legacyBackgroundKeyNight) {
763 	if (_useGlobalBackgroundKeys
764 		|| (!legacyBackgroundKeyDay && !legacyBackgroundKeyNight)) {
765 		return;
766 	}
767 	const auto move = [&](uint64 from, FileKey &to) {
768 		if (!from || to) {
769 			return;
770 		}
771 		to = GenerateKey(_basePath);
772 		FileReadDescriptor read;
773 		if (!ReadEncryptedFile(read, from, fromBasePath, fromLocalKey)) {
774 			return;
775 		}
776 		EncryptedDescriptor data;
777 		data.data = read.data;
778 		FileWriteDescriptor write(to, _basePath);
779 		write.writeEncrypted(data, SettingsKey);
780 	};
781 	move(legacyBackgroundKeyDay, _backgroundKeyDay);
782 	move(legacyBackgroundKeyNight, _backgroundKeyNight);
783 	_useGlobalBackgroundKeys = true;
784 	_settingsRewriteNeeded = true;
785 }
786 
reset()787 void reset() {
788 	if (_localLoader) {
789 		_localLoader->stop();
790 	}
791 
792 	Window::Theme::Background()->reset();
793 	_oldSettingsVersion = 0;
794 	Core::App().settings().resetOnLastLogout();
795 	writeSettings();
796 }
797 
oldSettingsVersion()798 int32 oldSettingsVersion() {
799 	return _oldSettingsVersion;
800 }
801 
802 class CountWaveformTask : public Task {
803 public:
CountWaveformTask(not_null<Data::DocumentMedia * > media)804 	CountWaveformTask(not_null<Data::DocumentMedia*> media)
805 	: _doc(media->owner())
806 	, _loc(_doc->location(true))
807 	, _data(media->bytes())
808 	, _wavemax(0) {
809 		if (_data.isEmpty() && !_loc.accessEnable()) {
810 			_doc = nullptr;
811 		}
812 	}
process()813 	void process() override {
814 		if (!_doc) return;
815 
816 		_waveform = audioCountWaveform(_loc, _data);
817 		_wavemax = _waveform.empty()
818 			? char(0)
819 			: *ranges::max_element(_waveform);
820 	}
finish()821 	void finish() override {
822 		if (const auto voice = _doc ? _doc->voice() : nullptr) {
823 			if (!_waveform.isEmpty()) {
824 				voice->waveform = _waveform;
825 				voice->wavemax = _wavemax;
826 			}
827 			if (voice->waveform.isEmpty()) {
828 				voice->waveform.resize(1);
829 				voice->waveform[0] = -2;
830 				voice->wavemax = 0;
831 			} else if (voice->waveform[0] < 0) {
832 				voice->waveform[0] = -2;
833 				voice->wavemax = 0;
834 			}
835 			_doc->owner().requestDocumentViewRepaint(_doc);
836 		}
837 	}
~CountWaveformTask()838 	~CountWaveformTask() {
839 		if (_data.isEmpty() && _doc) {
840 			_loc.accessDisable();
841 		}
842 	}
843 
844 protected:
845 	DocumentData *_doc = nullptr;
846 	Core::FileLocation _loc;
847 	QByteArray _data;
848 	VoiceWaveform _waveform;
849 	char _wavemax;
850 
851 };
852 
countVoiceWaveform(not_null<Data::DocumentMedia * > media)853 void countVoiceWaveform(not_null<Data::DocumentMedia*> media) {
854 	const auto document = media->owner();
855 	if (const auto voice = document->voice()) {
856 		if (_localLoader) {
857 			voice->waveform.resize(1 + sizeof(TaskId));
858 			voice->waveform[0] = -1; // counting
859 			TaskId taskId = _localLoader->addTask(
860 				std::make_unique<CountWaveformTask>(media));
861 			memcpy(voice->waveform.data() + 1, &taskId, sizeof(taskId));
862 		}
863 	}
864 }
865 
cancelTask(TaskId id)866 void cancelTask(TaskId id) {
867 	if (_localLoader) {
868 		_localLoader->cancelTask(id);
869 	}
870 }
871 
readThemeUsingKey(FileKey key)872 Window::Theme::Saved readThemeUsingKey(FileKey key) {
873 	using namespace Window::Theme;
874 
875 	FileReadDescriptor theme;
876 	if (!ReadEncryptedFile(theme, key, _basePath, SettingsKey)) {
877 		DEBUG_LOG(("Theme: Could not read file for key: %1").arg(key));
878 		return {};
879 	}
880 
881 	auto tag = QString();
882 	auto result = Saved();
883 	auto &object = result.object;
884 	auto &cache = result.cache;
885 	auto field1 = qint32();
886 	auto field2 = quint32();
887 	theme.stream >> object.content;
888 	theme.stream >> tag >> object.pathAbsolute;
889 	if (tag == kThemeNewPathRelativeTag) {
890 		theme.stream
891 			>> object.pathRelative
892 			>> object.cloud.id
893 			>> object.cloud.accessHash
894 			>> object.cloud.slug
895 			>> object.cloud.title
896 			>> object.cloud.documentId
897 			>> field1;
898 	} else {
899 		object.pathRelative = tag;
900 	}
901 	if (theme.stream.status() != QDataStream::Ok) {
902 		DEBUG_LOG(("Theme: Bad status for key: %1, tag: %2"
903 			).arg(key
904 			).arg(tag));
905 		return {};
906 	}
907 
908 	auto ignoreCache = false;
909 	if (!object.cloud.id) {
910 		auto file = QFile(object.pathRelative);
911 		if (object.pathRelative.isEmpty() || !file.exists()) {
912 			file.setFileName(object.pathAbsolute);
913 		}
914 		if (!file.fileName().isEmpty()
915 			&& file.exists()
916 			&& file.open(QIODevice::ReadOnly)) {
917 			if (file.size() > kThemeFileSizeLimit) {
918 				LOG(("Error: theme file too large: %1 "
919 					"(should be less than 5 MB, got %2)"
920 					).arg(file.fileName()
921 					).arg(file.size()));
922 				return {};
923 			}
924 			auto fileContent = file.readAll();
925 			file.close();
926 			if (object.content != fileContent) {
927 				object.content = fileContent;
928 				ignoreCache = true;
929 			}
930 		}
931 	}
932 	int32 cachePaletteChecksum = 0;
933 	int32 cacheContentChecksum = 0;
934 	QByteArray cacheColors;
935 	QByteArray cacheBackground;
936 	theme.stream
937 		>> cachePaletteChecksum
938 		>> cacheContentChecksum
939 		>> cacheColors
940 		>> cacheBackground
941 		>> field2;
942 	if (!ignoreCache) {
943 		if (theme.stream.status() != QDataStream::Ok) {
944 			DEBUG_LOG(("Theme: Bad status for cache, key: %1, tag: %2"
945 				).arg(key
946 				).arg(tag));
947 			return {};
948 		}
949 		cache.paletteChecksum = cachePaletteChecksum;
950 		cache.contentChecksum = cacheContentChecksum;
951 		cache.colors = std::move(cacheColors);
952 		cache.background = std::move(cacheBackground);
953 		cache.tiled = ((field2 & quint32(0xFF)) == 1);
954 	}
955 	if (tag == kThemeNewPathRelativeTag) {
956 		object.cloud.createdBy = UserId(
957 			((quint64(field2) >> 8) << 32) | quint64(quint32(field1)));
958 	}
959 	return result;
960 }
961 
InitialLoadThemeUsingKey(FileKey key)962 std::optional<QString> InitialLoadThemeUsingKey(FileKey key) {
963 	auto read = readThemeUsingKey(key);
964 	const auto result = read.object.pathAbsolute;
965 	if (read.object.content.isEmpty()) {
966 		DEBUG_LOG(("Theme: Could not read content for key: %1").arg(key));
967 	}
968 	if (read.object.content.isEmpty()
969 		|| !Window::Theme::Initialize(std::move(read))) {
970 		DEBUG_LOG(("Theme: Could not initialized for key: %1").arg(key));
971 		return std::nullopt;
972 	}
973 	return result;
974 }
975 
writeTheme(const Window::Theme::Saved & saved)976 void writeTheme(const Window::Theme::Saved &saved) {
977 	using namespace Window::Theme;
978 
979 	if (_themeKeyLegacy) {
980 		DEBUG_LOG(("Theme: skipping write, because legacy: %1"
981 			).arg(_themeKeyLegacy));
982 		return;
983 	}
984 	auto &themeKey = IsNightMode()
985 		? _themeKeyNight
986 		: _themeKeyDay;
987 	DEBUG_LOG(("Theme: writing (night: %1), key_day: %2, key_night: %3"
988 		).arg(Logs::b(IsNightMode())
989 		).arg(_themeKeyDay
990 		).arg(_themeKeyNight));
991 	if (saved.object.content.isEmpty()) {
992 		if (themeKey) {
993 			if (IsNightMode()) {
994 				DEBUG_LOG(("Theme: cleared for night mode."));
995 				SetNightModeValue(false);
996 			}
997 			ClearKey(themeKey, _basePath);
998 			themeKey = 0;
999 			writeSettings();
1000 		}
1001 		return;
1002 	}
1003 
1004 	if (!themeKey) {
1005 		themeKey = GenerateKey(_basePath);
1006 		writeSettings();
1007 	}
1008 
1009 	const auto &object = saved.object;
1010 	const auto &cache = saved.cache;
1011 	const auto tag = QString(kThemeNewPathRelativeTag);
1012 	quint32 size = Serialize::bytearraySize(object.content)
1013 		+ Serialize::stringSize(tag)
1014 		+ Serialize::stringSize(object.pathAbsolute)
1015 		+ Serialize::stringSize(object.pathRelative)
1016 		+ sizeof(uint64) * 3
1017 		+ Serialize::stringSize(object.cloud.slug)
1018 		+ Serialize::stringSize(object.cloud.title)
1019 		+ sizeof(qint32)
1020 		+ sizeof(qint32) * 2
1021 		+ Serialize::bytearraySize(cache.colors)
1022 		+ Serialize::bytearraySize(cache.background)
1023 		+ sizeof(quint32);
1024 	const auto bareCreatedById = object.cloud.createdBy.bare;
1025 	Assert((bareCreatedById & PeerId::kChatTypeMask) == bareCreatedById);
1026 	const auto field1 = qint32(quint32(bareCreatedById & 0xFFFFFFFFULL));
1027 	const auto field2 = quint32(cache.tiled ? 1 : 0)
1028 		| (quint32(bareCreatedById >> 32) << 8);
1029 	EncryptedDescriptor data(size);
1030 	data.stream
1031 		<< object.content
1032 		<< tag
1033 		<< object.pathAbsolute
1034 		<< object.pathRelative
1035 		<< object.cloud.id
1036 		<< object.cloud.accessHash
1037 		<< object.cloud.slug
1038 		<< object.cloud.title
1039 		<< object.cloud.documentId
1040 		<< field1
1041 		<< cache.paletteChecksum
1042 		<< cache.contentChecksum
1043 		<< cache.colors
1044 		<< cache.background
1045 		<< field2;
1046 
1047 	FileWriteDescriptor file(themeKey, _basePath);
1048 	file.writeEncrypted(data, SettingsKey);
1049 }
1050 
clearTheme()1051 void clearTheme() {
1052 	writeTheme(Window::Theme::Saved());
1053 }
1054 
InitialLoadTheme()1055 void InitialLoadTheme() {
1056 	const auto key = (_themeKeyLegacy != 0)
1057 		? _themeKeyLegacy
1058 		: (Window::Theme::IsNightMode()
1059 			? _themeKeyNight
1060 			: _themeKeyDay);
1061 	DEBUG_LOG(("Theme: initial load (night: %1), "
1062 		"key_legacy: %2, key_day: %3, key_night: %4"
1063 		).arg(Logs::b(Window::Theme::IsNightMode())
1064 		).arg(_themeKeyLegacy
1065 		).arg(_themeKeyDay
1066 		).arg(_themeKeyNight));
1067 	if (!key) {
1068 		if (Window::Theme::IsNightMode()) {
1069 			DEBUG_LOG(("Theme: zero key for night mode."));
1070 			Window::Theme::SetNightModeValue(false);
1071 		}
1072 		return;
1073 	} else if (const auto path = InitialLoadThemeUsingKey(key)) {
1074 		DEBUG_LOG(("Theme: loaded with result: %1").arg(*path));
1075 		if (_themeKeyLegacy) {
1076 			Window::Theme::SetNightModeValue(*path
1077 				== Window::Theme::NightThemePath());
1078 			(Window::Theme::IsNightMode()
1079 				? _themeKeyNight
1080 				: _themeKeyDay) = base::take(_themeKeyLegacy);
1081 			DEBUG_LOG(("Theme: now (night: %1), "
1082 				"key_legacy: %2, key_day: %3, key_night: %4 (path: %5)"
1083 				).arg(Logs::b(Window::Theme::IsNightMode())
1084 				).arg(_themeKeyLegacy
1085 				).arg(_themeKeyDay
1086 				).arg(_themeKeyNight
1087 				).arg(*path));
1088 		}
1089 	} else {
1090 		DEBUG_LOG(("Theme: could not load, clearing.."));
1091 		clearTheme();
1092 	}
1093 }
1094 
ApplyDefaultNightMode()1095 bool ApplyDefaultNightMode() {
1096 	const auto NightByDefault = Platform::IsMacStoreBuild();
1097 	if (!NightByDefault
1098 		|| Window::Theme::IsNightMode()
1099 		|| _themeKeyDay
1100 		|| _themeKeyNight
1101 		|| _themeKeyLegacy) {
1102 		return false;
1103 	}
1104 	Core::App().startSettingsAndBackground();
1105 	Window::Theme::ToggleNightMode();
1106 	Window::Theme::KeepApplied();
1107 	return true;
1108 }
1109 
readThemeAfterSwitch()1110 Window::Theme::Saved readThemeAfterSwitch() {
1111 	const auto key = Window::Theme::IsNightMode()
1112 		? _themeKeyNight
1113 		: _themeKeyDay;
1114 	return readThemeUsingKey(key);
1115 }
1116 
readLangPack()1117 void readLangPack() {
1118 	FileReadDescriptor langpack;
1119 	if (!_langPackKey || !ReadEncryptedFile(langpack, _langPackKey, _basePath, SettingsKey)) {
1120 		return;
1121 	}
1122 	auto data = QByteArray();
1123 	langpack.stream >> data;
1124 	if (langpack.stream.status() == QDataStream::Ok) {
1125 		Lang::GetInstance().fillFromSerialized(data, langpack.version);
1126 	}
1127 }
1128 
writeLangPack()1129 void writeLangPack() {
1130 	auto langpack = Lang::GetInstance().serialize();
1131 	if (!_langPackKey) {
1132 		_langPackKey = GenerateKey(_basePath);
1133 		writeSettings();
1134 	}
1135 
1136 	EncryptedDescriptor data(Serialize::bytearraySize(langpack));
1137 	data.stream << langpack;
1138 
1139 	FileWriteDescriptor file(_langPackKey, _basePath);
1140 	file.writeEncrypted(data, SettingsKey);
1141 }
1142 
saveRecentLanguages(const std::vector<Lang::Language> & list)1143 void saveRecentLanguages(const std::vector<Lang::Language> &list) {
1144 	if (list.empty()) {
1145 		if (_languagesKey) {
1146 			ClearKey(_languagesKey, _basePath);
1147 			_languagesKey = 0;
1148 			writeSettings();
1149 		}
1150 		return;
1151 	}
1152 
1153 	auto size = sizeof(qint32);
1154 	for (const auto &language : list) {
1155 		size += Serialize::stringSize(language.id)
1156 			+ Serialize::stringSize(language.pluralId)
1157 			+ Serialize::stringSize(language.baseId)
1158 			+ Serialize::stringSize(language.name)
1159 			+ Serialize::stringSize(language.nativeName);
1160 	}
1161 	if (!_languagesKey) {
1162 		_languagesKey = GenerateKey(_basePath);
1163 		writeSettings();
1164 	}
1165 
1166 	EncryptedDescriptor data(size);
1167 	data.stream << qint32(list.size());
1168 	for (const auto &language : list) {
1169 		data.stream
1170 			<< language.id
1171 			<< language.pluralId
1172 			<< language.baseId
1173 			<< language.name
1174 			<< language.nativeName;
1175 	}
1176 
1177 	FileWriteDescriptor file(_languagesKey, _basePath);
1178 	file.writeEncrypted(data, SettingsKey);
1179 }
1180 
pushRecentLanguage(const Lang::Language & language)1181 void pushRecentLanguage(const Lang::Language &language) {
1182 	if (language.id.startsWith('#')) {
1183 		return;
1184 	}
1185 	auto list = readRecentLanguages();
1186 	list.erase(
1187 		ranges::remove_if(
1188 			list,
1189 			[&](const Lang::Language &v) { return (v.id == language.id); }),
1190 		end(list));
1191 	list.insert(list.begin(), language);
1192 
1193 	saveRecentLanguages(list);
1194 }
1195 
removeRecentLanguage(const QString & id)1196 void removeRecentLanguage(const QString &id) {
1197 	auto list = readRecentLanguages();
1198 	list.erase(
1199 		ranges::remove_if(
1200 			list,
1201 			[&](const Lang::Language &v) { return (v.id == id); }),
1202 		end(list));
1203 
1204 	saveRecentLanguages(list);
1205 }
1206 
readRecentLanguages()1207 std::vector<Lang::Language> readRecentLanguages() {
1208 	FileReadDescriptor languages;
1209 	if (!_languagesKey || !ReadEncryptedFile(languages, _languagesKey, _basePath, SettingsKey)) {
1210 		return {};
1211 	}
1212 	qint32 count = 0;
1213 	languages.stream >> count;
1214 	if (count <= 0) {
1215 		return {};
1216 	}
1217 	auto result = std::vector<Lang::Language>();
1218 	result.reserve(count);
1219 	for (auto i = 0; i != count; ++i) {
1220 		auto language = Lang::Language();
1221 		languages.stream
1222 			>> language.id
1223 			>> language.pluralId
1224 			>> language.baseId
1225 			>> language.name
1226 			>> language.nativeName;
1227 		result.push_back(language);
1228 	}
1229 	if (languages.stream.status() != QDataStream::Ok) {
1230 		return {};
1231 	}
1232 	return result;
1233 }
1234 
ReadThemeContent()1235 Window::Theme::Object ReadThemeContent() {
1236 	using namespace Window::Theme;
1237 
1238 	auto &themeKey = IsNightMode() ? _themeKeyNight : _themeKeyDay;
1239 	if (!themeKey) {
1240 		return Object();
1241 	}
1242 
1243 	FileReadDescriptor theme;
1244 	if (!ReadEncryptedFile(theme, themeKey, _basePath, SettingsKey)) {
1245 		return Object();
1246 	}
1247 
1248 	QByteArray content;
1249 	QString pathRelative, pathAbsolute;
1250 	theme.stream >> content >> pathRelative >> pathAbsolute;
1251 	if (theme.stream.status() != QDataStream::Ok) {
1252 		return Object();
1253 	}
1254 
1255 	auto result = Object();
1256 	result.pathAbsolute = pathAbsolute;
1257 	result.content = content;
1258 	return result;
1259 }
1260 
incrementRecentHashtag(RecentHashtagPack & recent,const QString & tag)1261 void incrementRecentHashtag(RecentHashtagPack &recent, const QString &tag) {
1262 	auto i = recent.begin(), e = recent.end();
1263 	for (; i != e; ++i) {
1264 		if (i->first == tag) {
1265 			++i->second;
1266 			if (qAbs(i->second) > 0x4000) {
1267 				for (auto j = recent.begin(); j != e; ++j) {
1268 					if (j->second > 1) {
1269 						j->second /= 2;
1270 					} else if (j->second > 0) {
1271 						j->second = 1;
1272 					}
1273 				}
1274 			}
1275 			for (; i != recent.begin(); --i) {
1276 				if (qAbs((i - 1)->second) > qAbs(i->second)) {
1277 					break;
1278 				}
1279 				qSwap(*i, *(i - 1));
1280 			}
1281 			break;
1282 		}
1283 	}
1284 	if (i == e) {
1285 		while (recent.size() >= 64) recent.pop_back();
1286 		recent.push_back(qMakePair(tag, 1));
1287 		for (i = recent.end() - 1; i != recent.begin(); --i) {
1288 			if ((i - 1)->second > i->second) {
1289 				break;
1290 			}
1291 			qSwap(*i, *(i - 1));
1292 		}
1293 	}
1294 }
1295 
readOldMtpData(bool remove,ReadSettingsContext & context)1296 bool readOldMtpData(bool remove, ReadSettingsContext &context) {
1297 	return _readOldMtpData(remove, context);
1298 }
1299 
readOldUserSettings(bool remove,ReadSettingsContext & context)1300 bool readOldUserSettings(bool remove, ReadSettingsContext &context) {
1301 	return _readOldUserSettings(remove, context);
1302 }
1303 
1304 } // namespace Local
1305