2749 lines
		
	
	
	
		
			77 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2749 lines
		
	
	
	
		
			77 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
| This file is part of Telegram Desktop,
 | |
| the official desktop application for the Telegram messaging service.
 | |
| 
 | |
| For license and copyright information please follow this link:
 | |
| https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | |
| */
 | |
| #include "storage/storage_account.h"
 | |
| 
 | |
| #include "storage/localstorage.h"
 | |
| #include "storage/storage_domain.h"
 | |
| #include "storage/storage_encryption.h"
 | |
| #include "storage/storage_clear_legacy.h"
 | |
| #include "storage/cache/storage_cache_types.h"
 | |
| #include "storage/details/storage_file_utilities.h"
 | |
| #include "storage/details/storage_settings_scheme.h"
 | |
| #include "storage/serialize_common.h"
 | |
| #include "storage/serialize_peer.h"
 | |
| #include "storage/serialize_document.h"
 | |
| #include "main/main_account.h"
 | |
| #include "main/main_session.h"
 | |
| #include "mtproto/mtproto_config.h"
 | |
| #include "mtproto/mtproto_dc_options.h"
 | |
| #include "mtproto/mtp_instance.h"
 | |
| #include "history/history.h"
 | |
| #include "core/application.h"
 | |
| #include "core/file_location.h"
 | |
| #include "data/stickers/data_stickers.h"
 | |
| #include "data/data_session.h"
 | |
| #include "data/data_document.h"
 | |
| #include "data/data_user.h"
 | |
| #include "data/data_drafts.h"
 | |
| #include "export/export_settings.h"
 | |
| #include "window/themes/window_theme.h"
 | |
| 
 | |
| namespace Storage {
 | |
| namespace {
 | |
| 
 | |
| using namespace details;
 | |
| using Database = Cache::Database;
 | |
| 
 | |
| constexpr auto kDelayedWriteTimeout = crl::time(1000);
 | |
| 
 | |
| constexpr auto kStickersVersionTag = quint32(-1);
 | |
| constexpr auto kStickersSerializeVersion = 1;
 | |
| constexpr auto kMaxSavedStickerSetsCount = 1000;
 | |
| constexpr auto kDefaultStickerInstallDate = TimeId(1);
 | |
| 
 | |
| constexpr auto kSinglePeerTypeUser = qint32(1);
 | |
| constexpr auto kSinglePeerTypeChat = qint32(2);
 | |
| constexpr auto kSinglePeerTypeChannel = qint32(3);
 | |
| constexpr auto kSinglePeerTypeSelf = qint32(4);
 | |
| constexpr auto kSinglePeerTypeEmpty = qint32(0);
 | |
| constexpr auto kMultiDraftTag = quint64(0xFFFFFFFFFFFFFF01ULL);
 | |
| 
 | |
| enum { // Local Storage Keys
 | |
| 	lskUserMap = 0x00,
 | |
| 	lskDraft = 0x01, // data: PeerId peer
 | |
| 	lskDraftPosition = 0x02, // data: PeerId peer
 | |
| 	lskLegacyImages = 0x03, // legacy
 | |
| 	lskLocations = 0x04, // no data
 | |
| 	lskLegacyStickerImages = 0x05, // legacy
 | |
| 	lskLegacyAudios = 0x06, // legacy
 | |
| 	lskRecentStickersOld = 0x07, // no data
 | |
| 	lskBackgroundOldOld = 0x08, // no data
 | |
| 	lskUserSettings = 0x09, // no data
 | |
| 	lskRecentHashtagsAndBots = 0x0a, // no data
 | |
| 	lskStickersOld = 0x0b, // no data
 | |
| 	lskSavedPeersOld = 0x0c, // no data
 | |
| 	lskReportSpamStatusesOld = 0x0d, // no data
 | |
| 	lskSavedGifsOld = 0x0e, // no data
 | |
| 	lskSavedGifs = 0x0f, // no data
 | |
| 	lskStickersKeys = 0x10, // no data
 | |
| 	lskTrustedBots = 0x11, // no data
 | |
| 	lskFavedStickers = 0x12, // no data
 | |
| 	lskExportSettings = 0x13, // no data
 | |
| 	lskBackgroundOld = 0x14, // no data
 | |
| 	lskSelfSerialized = 0x15, // serialized self
 | |
| 	lskMasksKeys = 0x16, // no data
 | |
| };
 | |
| 
 | |
| [[nodiscard]] FileKey ComputeDataNameKey(const QString &dataName) {
 | |
| 	// We dropped old test authorizations when migrated to multi auth.
 | |
| 	//const auto testAddition = (cTestMode() ? qsl(":/test/") : QString());
 | |
| 	const auto testAddition = QString();
 | |
| 	const auto dataNameUtf8 = (dataName + testAddition).toUtf8();
 | |
| 	FileKey dataNameHash[2] = { 0 };
 | |
| 	hashMd5(dataNameUtf8.constData(), dataNameUtf8.size(), dataNameHash);
 | |
| 	return dataNameHash[0];
 | |
| }
 | |
| 
 | |
| [[nodiscard]] QString BaseGlobalPath() {
 | |
| 	return cWorkingDir() + qsl("tdata/");
 | |
| }
 | |
| 
 | |
| [[nodiscard]] QString ComputeDatabasePath(const QString &dataName) {
 | |
| 	return BaseGlobalPath()
 | |
| 		+ "user_" + dataName
 | |
| 		// We dropped old test authorizations when migrated to multi auth.
 | |
| 		//+ (cTestMode() ? "[test]" : "")
 | |
| 		+ '/';
 | |
| }
 | |
| 
 | |
| [[nodiscard]] QString LegacyTempDirectory() {
 | |
| 	return cWorkingDir() + qsl("tdata/tdld/");
 | |
| }
 | |
| 
 | |
| } // namespace
 | |
| 
 | |
| Account::Account(not_null<Main::Account*> owner, const QString &dataName)
 | |
| : _owner(owner)
 | |
| , _dataName(dataName)
 | |
| , _dataNameKey(ComputeDataNameKey(dataName))
 | |
| , _basePath(BaseGlobalPath() + ToFilePart(_dataNameKey) + QChar('/'))
 | |
| , _tempPath(BaseGlobalPath() + "temp_" + _dataName + QChar('/'))
 | |
| , _databasePath(ComputeDatabasePath(dataName))
 | |
| , _cacheTotalSizeLimit(Database::Settings().totalSizeLimit)
 | |
| , _cacheBigFileTotalSizeLimit(Database::Settings().totalSizeLimit)
 | |
| , _cacheTotalTimeLimit(Database::Settings().totalTimeLimit)
 | |
| , _cacheBigFileTotalTimeLimit(Database::Settings().totalTimeLimit)
 | |
| , _writeMapTimer([=] { writeMap(); })
 | |
| , _writeLocationsTimer([=] { writeLocations(); }) {
 | |
| }
 | |
| 
 | |
| Account::~Account() {
 | |
| 	if (_localKey && _mapChanged) {
 | |
| 		writeMap();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| QString Account::tempDirectory() const {
 | |
| 	return _tempPath;
 | |
| }
 | |
| 
 | |
| StartResult Account::legacyStart(const QByteArray &passcode) {
 | |
| 	const auto result = readMapWith(MTP::AuthKeyPtr(), passcode);
 | |
| 	if (result == ReadMapResult::Failed) {
 | |
| 		Assert(_localKey == nullptr);
 | |
| 	} else if (result == ReadMapResult::IncorrectPasscode) {
 | |
| 		return StartResult::IncorrectPasscodeLegacy;
 | |
| 	}
 | |
| 	clearLegacyFiles();
 | |
| 	return StartResult::Success;
 | |
| }
 | |
| 
 | |
| std::unique_ptr<MTP::Config> Account::start(MTP::AuthKeyPtr localKey) {
 | |
| 	Expects(localKey != nullptr);
 | |
| 
 | |
| 	_localKey = std::move(localKey);
 | |
| 	readMapWith(_localKey);
 | |
| 	clearLegacyFiles();
 | |
| 	return readMtpConfig();
 | |
| }
 | |
| 
 | |
| void Account::startAdded(MTP::AuthKeyPtr localKey) {
 | |
| 	Expects(localKey != nullptr);
 | |
| 
 | |
| 	_localKey = std::move(localKey);
 | |
| 	clearLegacyFiles();
 | |
| }
 | |
| 
 | |
| void Account::clearLegacyFiles() {
 | |
| 	const auto weak = base::make_weak(_owner.get());
 | |
| 	ClearLegacyFiles(_basePath, [weak, this](
 | |
| 			FnMut<void(base::flat_set<QString>&&)> then) {
 | |
| 		crl::on_main(weak, [this, then = std::move(then)]() mutable {
 | |
| 			then(collectGoodNames());
 | |
| 		});
 | |
| 	});
 | |
| }
 | |
| 
 | |
| base::flat_set<QString> Account::collectGoodNames() const {
 | |
| 	const auto keys = {
 | |
| 		_locationsKey,
 | |
| 		_settingsKey,
 | |
| 		_installedStickersKey,
 | |
| 		_featuredStickersKey,
 | |
| 		_recentStickersKey,
 | |
| 		_favedStickersKey,
 | |
| 		_archivedStickersKey,
 | |
| 		_recentStickersKeyOld,
 | |
| 		_savedGifsKey,
 | |
| 		_legacyBackgroundKeyNight,
 | |
| 		_legacyBackgroundKeyDay,
 | |
| 		_recentHashtagsAndBotsKey,
 | |
| 		_exportSettingsKey,
 | |
| 		_trustedBotsKey,
 | |
| 		_installedMasksKey,
 | |
| 		_recentMasksKey,
 | |
| 		_archivedMasksKey,
 | |
| 	};
 | |
| 	auto result = base::flat_set<QString>{
 | |
| 		"map0",
 | |
| 		"map1",
 | |
| 		"maps",
 | |
| 		"configs",
 | |
| 	};
 | |
| 	const auto push = [&](FileKey key) {
 | |
| 		if (!key) {
 | |
| 			return;
 | |
| 		}
 | |
| 		auto name = ToFilePart(key) + '0';
 | |
| 		result.emplace(name);
 | |
| 		name[name.size() - 1] = '1';
 | |
| 		result.emplace(name);
 | |
| 		name[name.size() - 1] = 's';
 | |
| 		result.emplace(name);
 | |
| 	};
 | |
| 	for (const auto &[key, value] : _draftsMap) {
 | |
| 		push(value);
 | |
| 	}
 | |
| 	for (const auto &[key, value] : _draftCursorsMap) {
 | |
| 		push(value);
 | |
| 	}
 | |
| 	for (const auto &value : keys) {
 | |
| 		push(value);
 | |
| 	}
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| Account::ReadMapResult Account::readMapWith(
 | |
| 		MTP::AuthKeyPtr localKey,
 | |
| 		const QByteArray &legacyPasscode) {
 | |
| 	auto ms = crl::now();
 | |
| 
 | |
| 	FileReadDescriptor mapData;
 | |
| 	if (!ReadFile(mapData, qsl("map"), _basePath)) {
 | |
| 		return ReadMapResult::Failed;
 | |
| 	}
 | |
| 	LOG(("App Info: reading map..."));
 | |
| 
 | |
| 	QByteArray legacySalt, legacyKeyEncrypted, mapEncrypted;
 | |
| 	mapData.stream >> legacySalt >> legacyKeyEncrypted >> mapEncrypted;
 | |
| 	if (!CheckStreamStatus(mapData.stream)) {
 | |
| 		return ReadMapResult::Failed;
 | |
| 	}
 | |
| 	if (!localKey) {
 | |
| 		if (legacySalt.size() != LocalEncryptSaltSize) {
 | |
| 			LOG(("App Error: bad salt in map file, size: %1").arg(legacySalt.size()));
 | |
| 			return ReadMapResult::Failed;
 | |
| 		}
 | |
| 		auto legacyPasscodeKey = CreateLegacyLocalKey(legacyPasscode, legacySalt);
 | |
| 
 | |
| 		EncryptedDescriptor keyData;
 | |
| 		if (!DecryptLocal(keyData, legacyKeyEncrypted, legacyPasscodeKey)) {
 | |
| 			LOG(("App Info: could not decrypt pass-protected key from map file, maybe bad password..."));
 | |
| 			return ReadMapResult::IncorrectPasscode;
 | |
| 		}
 | |
| 		auto key = Serialize::read<MTP::AuthKey::Data>(keyData.stream);
 | |
| 		if (keyData.stream.status() != QDataStream::Ok || !keyData.stream.atEnd()) {
 | |
| 			LOG(("App Error: could not read pass-protected key from map file"));
 | |
| 			return ReadMapResult::Failed;
 | |
| 		}
 | |
| 		localKey = std::make_shared<MTP::AuthKey>(key);
 | |
| 	}
 | |
| 
 | |
| 	EncryptedDescriptor map;
 | |
| 	if (!DecryptLocal(map, mapEncrypted, localKey)) {
 | |
| 		LOG(("App Error: could not decrypt map."));
 | |
| 		return ReadMapResult::Failed;
 | |
| 	}
 | |
| 	LOG(("App Info: reading encrypted map..."));
 | |
| 
 | |
| 	QByteArray selfSerialized;
 | |
| 	base::flat_map<PeerId, FileKey> draftsMap;
 | |
| 	base::flat_map<PeerId, FileKey> draftCursorsMap;
 | |
| 	base::flat_map<PeerId, bool> draftsNotReadMap;
 | |
| 	quint64 locationsKey = 0, reportSpamStatusesKey = 0, trustedBotsKey = 0;
 | |
| 	quint64 recentStickersKeyOld = 0;
 | |
| 	quint64 installedStickersKey = 0, featuredStickersKey = 0, recentStickersKey = 0, favedStickersKey = 0, archivedStickersKey = 0;
 | |
| 	quint64 installedMasksKey = 0, recentMasksKey = 0, archivedMasksKey = 0;
 | |
| 	quint64 savedGifsKey = 0;
 | |
| 	quint64 legacyBackgroundKeyDay = 0, legacyBackgroundKeyNight = 0;
 | |
| 	quint64 userSettingsKey = 0, recentHashtagsAndBotsKey = 0, exportSettingsKey = 0;
 | |
| 	while (!map.stream.atEnd()) {
 | |
| 		quint32 keyType;
 | |
| 		map.stream >> keyType;
 | |
| 		switch (keyType) {
 | |
| 		case lskDraft: {
 | |
| 			quint32 count = 0;
 | |
| 			map.stream >> count;
 | |
| 			for (quint32 i = 0; i < count; ++i) {
 | |
| 				FileKey key;
 | |
| 				quint64 peerIdSerialized;
 | |
| 				map.stream >> key >> peerIdSerialized;
 | |
| 				const auto peerId = DeserializePeerId(peerIdSerialized);
 | |
| 				draftsMap.emplace(peerId, key);
 | |
| 				draftsNotReadMap.emplace(peerId, true);
 | |
| 			}
 | |
| 		} break;
 | |
| 		case lskSelfSerialized: {
 | |
| 			map.stream >> selfSerialized;
 | |
| 		} break;
 | |
| 		case lskDraftPosition: {
 | |
| 			quint32 count = 0;
 | |
| 			map.stream >> count;
 | |
| 			for (quint32 i = 0; i < count; ++i) {
 | |
| 				FileKey key;
 | |
| 				quint64 peerIdSerialized;
 | |
| 				map.stream >> key >> peerIdSerialized;
 | |
| 				const auto peerId = DeserializePeerId(peerIdSerialized);
 | |
| 				draftCursorsMap.emplace(peerId, key);
 | |
| 			}
 | |
| 		} break;
 | |
| 		case lskLegacyImages:
 | |
| 		case lskLegacyStickerImages:
 | |
| 		case lskLegacyAudios: {
 | |
| 			quint32 count = 0;
 | |
| 			map.stream >> count;
 | |
| 			for (quint32 i = 0; i < count; ++i) {
 | |
| 				FileKey key;
 | |
| 				quint64 first, second;
 | |
| 				qint32 size;
 | |
| 				map.stream >> key >> first >> second >> size;
 | |
| 				// Just ignore the key, it will be removed as a leaked one.
 | |
| 			}
 | |
| 		} break;
 | |
| 		case lskLocations: {
 | |
| 			map.stream >> locationsKey;
 | |
| 		} break;
 | |
| 		case lskReportSpamStatusesOld: {
 | |
| 			map.stream >> reportSpamStatusesKey;
 | |
| 			ClearKey(reportSpamStatusesKey, _basePath);
 | |
| 		} break;
 | |
| 		case lskTrustedBots: {
 | |
| 			map.stream >> trustedBotsKey;
 | |
| 		} break;
 | |
| 		case lskRecentStickersOld: {
 | |
| 			map.stream >> recentStickersKeyOld;
 | |
| 		} break;
 | |
| 		case lskBackgroundOldOld: {
 | |
| 			map.stream >> (Window::Theme::IsNightMode()
 | |
| 				? legacyBackgroundKeyNight
 | |
| 				: legacyBackgroundKeyDay);
 | |
| 		} break;
 | |
| 		case lskBackgroundOld: {
 | |
| 			map.stream >> legacyBackgroundKeyDay >> legacyBackgroundKeyNight;
 | |
| 		} break;
 | |
| 		case lskUserSettings: {
 | |
| 			map.stream >> userSettingsKey;
 | |
| 		} break;
 | |
| 		case lskRecentHashtagsAndBots: {
 | |
| 			map.stream >> recentHashtagsAndBotsKey;
 | |
| 		} break;
 | |
| 		case lskStickersOld: {
 | |
| 			map.stream >> installedStickersKey;
 | |
| 		} break;
 | |
| 		case lskStickersKeys: {
 | |
| 			map.stream >> installedStickersKey >> featuredStickersKey >> recentStickersKey >> archivedStickersKey;
 | |
| 		} break;
 | |
| 		case lskFavedStickers: {
 | |
| 			map.stream >> favedStickersKey;
 | |
| 		} break;
 | |
| 		case lskSavedGifsOld: {
 | |
| 			quint64 key;
 | |
| 			map.stream >> key;
 | |
| 		} break;
 | |
| 		case lskSavedGifs: {
 | |
| 			map.stream >> savedGifsKey;
 | |
| 		} break;
 | |
| 		case lskSavedPeersOld: {
 | |
| 			quint64 key;
 | |
| 			map.stream >> key;
 | |
| 		} break;
 | |
| 		case lskExportSettings: {
 | |
| 			map.stream >> exportSettingsKey;
 | |
| 		} break;
 | |
| 		case lskMasksKeys: {
 | |
| 			map.stream
 | |
| 				>> installedMasksKey
 | |
| 				>> recentMasksKey
 | |
| 				>> archivedMasksKey;
 | |
| 		} break;
 | |
| 		default:
 | |
| 			LOG(("App Error: unknown key type in encrypted map: %1").arg(keyType));
 | |
| 			return ReadMapResult::Failed;
 | |
| 		}
 | |
| 		if (!CheckStreamStatus(map.stream)) {
 | |
| 			return ReadMapResult::Failed;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	_localKey = std::move(localKey);
 | |
| 
 | |
| 	_draftsMap = draftsMap;
 | |
| 	_draftCursorsMap = draftCursorsMap;
 | |
| 	_draftsNotReadMap = draftsNotReadMap;
 | |
| 
 | |
| 	_locationsKey = locationsKey;
 | |
| 	_trustedBotsKey = trustedBotsKey;
 | |
| 	_recentStickersKeyOld = recentStickersKeyOld;
 | |
| 	_installedStickersKey = installedStickersKey;
 | |
| 	_featuredStickersKey = featuredStickersKey;
 | |
| 	_recentStickersKey = recentStickersKey;
 | |
| 	_favedStickersKey = favedStickersKey;
 | |
| 	_archivedStickersKey = archivedStickersKey;
 | |
| 	_savedGifsKey = savedGifsKey;
 | |
| 	_installedMasksKey = installedMasksKey;
 | |
| 	_recentMasksKey = recentMasksKey;
 | |
| 	_archivedMasksKey = archivedMasksKey;
 | |
| 	_legacyBackgroundKeyDay = legacyBackgroundKeyDay;
 | |
| 	_legacyBackgroundKeyNight = legacyBackgroundKeyNight;
 | |
| 	_settingsKey = userSettingsKey;
 | |
| 	_recentHashtagsAndBotsKey = recentHashtagsAndBotsKey;
 | |
| 	_exportSettingsKey = exportSettingsKey;
 | |
| 	_oldMapVersion = mapData.version;
 | |
| 
 | |
| 	if (_oldMapVersion < AppVersion) {
 | |
| 		writeMapDelayed();
 | |
| 	} else {
 | |
| 		_mapChanged = false;
 | |
| 	}
 | |
| 
 | |
| 	if (_locationsKey) {
 | |
| 		readLocations();
 | |
| 	}
 | |
| 	if (_legacyBackgroundKeyDay || _legacyBackgroundKeyNight) {
 | |
| 		Local::moveLegacyBackground(
 | |
| 			_basePath,
 | |
| 			_localKey,
 | |
| 			_legacyBackgroundKeyDay,
 | |
| 			_legacyBackgroundKeyNight);
 | |
| 	}
 | |
| 
 | |
| 	auto stored = readSessionSettings();
 | |
| 	readMtpData();
 | |
| 
 | |
| 	DEBUG_LOG(("selfSerialized set: %1").arg(selfSerialized.size()));
 | |
| 	_owner->setSessionFromStorage(
 | |
| 		std::move(stored),
 | |
| 		std::move(selfSerialized),
 | |
| 		_oldMapVersion);
 | |
| 
 | |
| 	LOG(("Map read time: %1").arg(crl::now() - ms));
 | |
| 
 | |
| 	return ReadMapResult::Success;
 | |
| }
 | |
| 
 | |
| void Account::writeMapDelayed() {
 | |
| 	_mapChanged = true;
 | |
| 	_writeMapTimer.callOnce(kDelayedWriteTimeout);
 | |
| }
 | |
| 
 | |
| void Account::writeMapQueued() {
 | |
| 	_mapChanged = true;
 | |
| 	crl::on_main(_owner, [=] {
 | |
| 		writeMap();
 | |
| 	});
 | |
| }
 | |
| 
 | |
| void Account::writeMap() {
 | |
| 	Expects(_localKey != nullptr);
 | |
| 
 | |
| 	_writeMapTimer.cancel();
 | |
| 	if (!_mapChanged) {
 | |
| 		return;
 | |
| 	}
 | |
| 	_mapChanged = false;
 | |
| 
 | |
| 	if (!QDir().exists(_basePath)) {
 | |
| 		QDir().mkpath(_basePath);
 | |
| 	}
 | |
| 
 | |
| 	FileWriteDescriptor map(u"map"_q, _basePath);
 | |
| 	map.writeData(QByteArray());
 | |
| 	map.writeData(QByteArray());
 | |
| 
 | |
| 	uint32 mapSize = 0;
 | |
| 	const auto self = [&] {
 | |
| 		if (!_owner->sessionExists()) {
 | |
| 			DEBUG_LOG(("AuthSelf Warning: Session does not exist."));
 | |
| 			return QByteArray();
 | |
| 		}
 | |
| 		const auto self = _owner->session().user();
 | |
| 		if (self->phone().isEmpty()) {
 | |
| 			DEBUG_LOG(("AuthSelf Error: Phone is empty."));
 | |
| 			return QByteArray();
 | |
| 		}
 | |
| 		auto result = QByteArray();
 | |
| 		result.reserve(Serialize::peerSize(self)
 | |
| 			+ Serialize::stringSize(self->about()));
 | |
| 		{
 | |
| 			QBuffer buffer(&result);
 | |
| 			buffer.open(QIODevice::WriteOnly);
 | |
| 			QDataStream stream(&buffer);
 | |
| 			Serialize::writePeer(stream, self);
 | |
| 			stream << self->about();
 | |
| 		}
 | |
| 		return result;
 | |
| 	}();
 | |
| 	if (!self.isEmpty()) mapSize += sizeof(quint32) + Serialize::bytearraySize(self);
 | |
| 	if (!_draftsMap.empty()) mapSize += sizeof(quint32) * 2 + _draftsMap.size() * sizeof(quint64) * 2;
 | |
| 	if (!_draftCursorsMap.empty()) mapSize += sizeof(quint32) * 2 + _draftCursorsMap.size() * sizeof(quint64) * 2;
 | |
| 	if (_locationsKey) mapSize += sizeof(quint32) + sizeof(quint64);
 | |
| 	if (_trustedBotsKey) mapSize += sizeof(quint32) + sizeof(quint64);
 | |
| 	if (_recentStickersKeyOld) mapSize += sizeof(quint32) + sizeof(quint64);
 | |
| 	if (_installedStickersKey || _featuredStickersKey || _recentStickersKey || _archivedStickersKey) {
 | |
| 		mapSize += sizeof(quint32) + 4 * sizeof(quint64);
 | |
| 	}
 | |
| 	if (_favedStickersKey) mapSize += sizeof(quint32) + sizeof(quint64);
 | |
| 	if (_savedGifsKey) mapSize += sizeof(quint32) + sizeof(quint64);
 | |
| 	if (_settingsKey) mapSize += sizeof(quint32) + sizeof(quint64);
 | |
| 	if (_recentHashtagsAndBotsKey) mapSize += sizeof(quint32) + sizeof(quint64);
 | |
| 	if (_exportSettingsKey) mapSize += sizeof(quint32) + sizeof(quint64);
 | |
| 	if (_installedMasksKey || _recentMasksKey || _archivedMasksKey) {
 | |
| 		mapSize += sizeof(quint32) + 3 * sizeof(quint64);
 | |
| 	}
 | |
| 
 | |
| 	EncryptedDescriptor mapData(mapSize);
 | |
| 	if (!self.isEmpty()) {
 | |
| 		mapData.stream << quint32(lskSelfSerialized) << self;
 | |
| 	}
 | |
| 	if (!_draftsMap.empty()) {
 | |
| 		mapData.stream << quint32(lskDraft) << quint32(_draftsMap.size());
 | |
| 		for (const auto &[key, value] : _draftsMap) {
 | |
| 			mapData.stream << quint64(value) << SerializePeerId(key);
 | |
| 		}
 | |
| 	}
 | |
| 	if (!_draftCursorsMap.empty()) {
 | |
| 		mapData.stream << quint32(lskDraftPosition) << quint32(_draftCursorsMap.size());
 | |
| 		for (const auto &[key, value] : _draftCursorsMap) {
 | |
| 			mapData.stream << quint64(value) << SerializePeerId(key);
 | |
| 		}
 | |
| 	}
 | |
| 	if (_locationsKey) {
 | |
| 		mapData.stream << quint32(lskLocations) << quint64(_locationsKey);
 | |
| 	}
 | |
| 	if (_trustedBotsKey) {
 | |
| 		mapData.stream << quint32(lskTrustedBots) << quint64(_trustedBotsKey);
 | |
| 	}
 | |
| 	if (_recentStickersKeyOld) {
 | |
| 		mapData.stream << quint32(lskRecentStickersOld) << quint64(_recentStickersKeyOld);
 | |
| 	}
 | |
| 	if (_installedStickersKey || _featuredStickersKey || _recentStickersKey || _archivedStickersKey) {
 | |
| 		mapData.stream << quint32(lskStickersKeys);
 | |
| 		mapData.stream << quint64(_installedStickersKey) << quint64(_featuredStickersKey) << quint64(_recentStickersKey) << quint64(_archivedStickersKey);
 | |
| 	}
 | |
| 	if (_favedStickersKey) {
 | |
| 		mapData.stream << quint32(lskFavedStickers) << quint64(_favedStickersKey);
 | |
| 	}
 | |
| 	if (_savedGifsKey) {
 | |
| 		mapData.stream << quint32(lskSavedGifs) << quint64(_savedGifsKey);
 | |
| 	}
 | |
| 	if (_settingsKey) {
 | |
| 		mapData.stream << quint32(lskUserSettings) << quint64(_settingsKey);
 | |
| 	}
 | |
| 	if (_recentHashtagsAndBotsKey) {
 | |
| 		mapData.stream << quint32(lskRecentHashtagsAndBots) << quint64(_recentHashtagsAndBotsKey);
 | |
| 	}
 | |
| 	if (_exportSettingsKey) {
 | |
| 		mapData.stream << quint32(lskExportSettings) << quint64(_exportSettingsKey);
 | |
| 	}
 | |
| 	if (_installedMasksKey || _recentMasksKey || _archivedMasksKey) {
 | |
| 		mapData.stream << quint32(lskMasksKeys);
 | |
| 		mapData.stream
 | |
| 			<< quint64(_installedMasksKey)
 | |
| 			<< quint64(_recentMasksKey)
 | |
| 			<< quint64(_archivedMasksKey);
 | |
| 	}
 | |
| 	map.writeEncrypted(mapData, _localKey);
 | |
| 
 | |
| 	_mapChanged = false;
 | |
| }
 | |
| 
 | |
| void Account::reset() {
 | |
| 	auto names = collectGoodNames();
 | |
| 	_draftsMap.clear();
 | |
| 	_draftCursorsMap.clear();
 | |
| 	_draftsNotReadMap.clear();
 | |
| 	_locationsKey = _trustedBotsKey = 0;
 | |
| 	_recentStickersKeyOld = 0;
 | |
| 	_installedStickersKey = 0;
 | |
| 	_featuredStickersKey = 0;
 | |
| 	_recentStickersKey = 0;
 | |
| 	_favedStickersKey = 0;
 | |
| 	_archivedStickersKey = 0;
 | |
| 	_savedGifsKey = 0;
 | |
| 	_installedMasksKey = 0;
 | |
| 	_recentMasksKey = 0;
 | |
| 	_archivedMasksKey = 0;
 | |
| 	_legacyBackgroundKeyDay = _legacyBackgroundKeyNight = 0;
 | |
| 	_settingsKey = _recentHashtagsAndBotsKey = _exportSettingsKey = 0;
 | |
| 	_oldMapVersion = 0;
 | |
| 	_fileLocations.clear();
 | |
| 	_fileLocationPairs.clear();
 | |
| 	_fileLocationAliases.clear();
 | |
| 	_cacheTotalSizeLimit = Database::Settings().totalSizeLimit;
 | |
| 	_cacheTotalTimeLimit = Database::Settings().totalTimeLimit;
 | |
| 	_cacheBigFileTotalSizeLimit = Database::Settings().totalSizeLimit;
 | |
| 	_cacheBigFileTotalTimeLimit = Database::Settings().totalTimeLimit;
 | |
| 	_mapChanged = true;
 | |
| 	writeMap();
 | |
| 	writeMtpData();
 | |
| 
 | |
| 	crl::async([base = _basePath, temp = _tempPath, names = std::move(names)] {
 | |
| 		for (const auto &name : names) {
 | |
| 			if (!name.endsWith(qstr("map0"))
 | |
| 				&& !name.endsWith(qstr("map1"))
 | |
| 				&& !name.endsWith(qstr("maps"))
 | |
| 				&& !name.endsWith(qstr("configs"))) {
 | |
| 				QFile::remove(base + name);
 | |
| 			}
 | |
| 		}
 | |
| 		QDir(LegacyTempDirectory()).removeRecursively();
 | |
| 		QDir(temp).removeRecursively();
 | |
| 	});
 | |
| 
 | |
| 	Local::sync();
 | |
| }
 | |
| 
 | |
| void Account::writeLocations() {
 | |
| 	_writeLocationsTimer.cancel();
 | |
| 	if (!_locationsChanged) {
 | |
| 		return;
 | |
| 	}
 | |
| 	_locationsChanged = false;
 | |
| 
 | |
| 	if (_fileLocations.isEmpty()) {
 | |
| 		if (_locationsKey) {
 | |
| 			ClearKey(_locationsKey, _basePath);
 | |
| 			_locationsKey = 0;
 | |
| 			writeMapDelayed();
 | |
| 		}
 | |
| 	} else {
 | |
| 		if (!_locationsKey) {
 | |
| 			_locationsKey = GenerateKey(_basePath);
 | |
| 			writeMapQueued();
 | |
| 		}
 | |
| 		quint32 size = 0;
 | |
| 		for (auto i = _fileLocations.cbegin(), e = _fileLocations.cend(); i != e; ++i) {
 | |
| 			// location + type + namelen + name
 | |
| 			size += sizeof(quint64) * 2 + sizeof(quint32) + Serialize::stringSize(i.value().name());
 | |
| 			if (AppVersion > 9013) {
 | |
| 				// bookmark
 | |
| 				size += Serialize::bytearraySize(i.value().bookmark());
 | |
| 			}
 | |
| 			// date + size
 | |
| 			size += Serialize::dateTimeSize() + sizeof(quint32);
 | |
| 		}
 | |
| 
 | |
| 		//end mark
 | |
| 		size += sizeof(quint64) * 2 + sizeof(quint32) + Serialize::stringSize(QString());
 | |
| 		if (AppVersion > 9013) {
 | |
| 			size += Serialize::bytearraySize(QByteArray());
 | |
| 		}
 | |
| 		size += Serialize::dateTimeSize() + sizeof(quint32);
 | |
| 
 | |
| 		size += sizeof(quint32); // aliases count
 | |
| 		for (auto i = _fileLocationAliases.cbegin(), e = _fileLocationAliases.cend(); i != e; ++i) {
 | |
| 			// alias + location
 | |
| 			size += sizeof(quint64) * 2 + sizeof(quint64) * 2;
 | |
| 		}
 | |
| 
 | |
| 		EncryptedDescriptor data(size);
 | |
| 		auto legacyTypeField = 0;
 | |
| 		for (auto i = _fileLocations.cbegin(); i != _fileLocations.cend(); ++i) {
 | |
| 			data.stream << quint64(i.key().first) << quint64(i.key().second) << quint32(legacyTypeField) << i.value().name();
 | |
| 			if (AppVersion > 9013) {
 | |
| 				data.stream << i.value().bookmark();
 | |
| 			}
 | |
| 			data.stream << i.value().modified << quint32(i.value().size);
 | |
| 		}
 | |
| 
 | |
| 		data.stream << quint64(0) << quint64(0) << quint32(0) << QString();
 | |
| 		if (AppVersion > 9013) {
 | |
| 			data.stream << QByteArray();
 | |
| 		}
 | |
| 		data.stream << QDateTime::currentDateTime() << quint32(0);
 | |
| 
 | |
| 		data.stream << quint32(_fileLocationAliases.size());
 | |
| 		for (auto i = _fileLocationAliases.cbegin(), e = _fileLocationAliases.cend(); i != e; ++i) {
 | |
| 			data.stream << quint64(i.key().first) << quint64(i.key().second) << quint64(i.value().first) << quint64(i.value().second);
 | |
| 		}
 | |
| 
 | |
| 		FileWriteDescriptor file(_locationsKey, _basePath);
 | |
| 		file.writeEncrypted(data, _localKey);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void Account::writeLocationsQueued() {
 | |
| 	_locationsChanged = true;
 | |
| 	crl::on_main(_owner, [=] {
 | |
| 		writeLocations();
 | |
| 	});
 | |
| }
 | |
| 
 | |
| void Account::writeLocationsDelayed() {
 | |
| 	_locationsChanged = true;
 | |
| 	_writeLocationsTimer.callOnce(kDelayedWriteTimeout);
 | |
| }
 | |
| 
 | |
| void Account::readLocations() {
 | |
| 	FileReadDescriptor locations;
 | |
| 	if (!ReadEncryptedFile(locations, _locationsKey, _basePath, _localKey)) {
 | |
| 		ClearKey(_locationsKey, _basePath);
 | |
| 		_locationsKey = 0;
 | |
| 		writeMapDelayed();
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	bool endMarkFound = false;
 | |
| 	while (!locations.stream.atEnd()) {
 | |
| 		quint64 first, second;
 | |
| 		QByteArray bookmark;
 | |
| 		Core::FileLocation loc;
 | |
| 		quint32 legacyTypeField = 0;
 | |
| 		locations.stream >> first >> second >> legacyTypeField >> loc.fname;
 | |
| 		if (locations.version > 9013) {
 | |
| 			locations.stream >> bookmark;
 | |
| 		}
 | |
| 		locations.stream >> loc.modified >> loc.size;
 | |
| 		loc.setBookmark(bookmark);
 | |
| 
 | |
| 		if (!first && !second && !legacyTypeField && loc.fname.isEmpty() && !loc.size) { // end mark
 | |
| 			endMarkFound = true;
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		MediaKey key(first, second);
 | |
| 
 | |
| 		_fileLocations.insert(key, loc);
 | |
| 		if (!loc.inMediaCache()) {
 | |
| 			_fileLocationPairs.insert(loc.fname, { key, loc });
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (endMarkFound) {
 | |
| 		quint32 cnt;
 | |
| 		locations.stream >> cnt;
 | |
| 		for (quint32 i = 0; i < cnt; ++i) {
 | |
| 			quint64 kfirst, ksecond, vfirst, vsecond;
 | |
| 			locations.stream >> kfirst >> ksecond >> vfirst >> vsecond;
 | |
| 			_fileLocationAliases.insert(MediaKey(kfirst, ksecond), MediaKey(vfirst, vsecond));
 | |
| 		}
 | |
| 
 | |
| 		if (!locations.stream.atEnd()) {
 | |
| 			quint32 webLocationsCount;
 | |
| 			locations.stream >> webLocationsCount;
 | |
| 			for (quint32 i = 0; i < webLocationsCount; ++i) {
 | |
| 				QString url;
 | |
| 				quint64 key;
 | |
| 				qint32 size;
 | |
| 				locations.stream >> url >> key >> size;
 | |
| 				ClearKey(key, _basePath);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void Account::writeSessionSettings() {
 | |
| 	writeSessionSettings(nullptr);
 | |
| }
 | |
| 
 | |
| void Account::writeSessionSettings(Main::SessionSettings *stored) {
 | |
| 	if (_readingUserSettings) {
 | |
| 		LOG(("App Error: attempt to write settings while reading them!"));
 | |
| 		return;
 | |
| 	}
 | |
| 	LOG(("App Info: writing encrypted user settings..."));
 | |
| 
 | |
| 	if (!_settingsKey) {
 | |
| 		_settingsKey = GenerateKey(_basePath);
 | |
| 		writeMapQueued();
 | |
| 	}
 | |
| 
 | |
| 	auto userDataInstance = stored
 | |
| 		? stored
 | |
| 		: _owner->getSessionSettings();
 | |
| 	auto userData = userDataInstance
 | |
| 		? userDataInstance->serialize()
 | |
| 		: QByteArray();
 | |
| 
 | |
| 	auto recentStickers = cRecentStickersPreload();
 | |
| 	if (recentStickers.isEmpty() && _owner->sessionExists()) {
 | |
| 		const auto &stickers = _owner->session().data().stickers();
 | |
| 		recentStickers.reserve(stickers.getRecentPack().size());
 | |
| 		for (const auto &pair : std::as_const(stickers.getRecentPack())) {
 | |
| 			recentStickers.push_back(qMakePair(pair.first->id, pair.second));
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	uint32 size = 24 * (sizeof(quint32) + sizeof(qint32));
 | |
| 	size += sizeof(quint32);
 | |
| 	size += sizeof(quint32) + sizeof(qint32) + recentStickers.size() * (sizeof(uint64) + sizeof(ushort));
 | |
| 	size += sizeof(quint32) + 3 * sizeof(qint32);
 | |
| 	size += sizeof(quint32) + 2 * sizeof(qint32);
 | |
| 	size += sizeof(quint32) + sizeof(qint64) + sizeof(qint32);
 | |
| 	if (!userData.isEmpty()) {
 | |
| 		size += sizeof(quint32) + Serialize::bytearraySize(userData);
 | |
| 	}
 | |
| 
 | |
| 	EncryptedDescriptor data(size);
 | |
| 	data.stream << quint32(dbiUseExternalVideoPlayer) << qint32(cUseExternalVideoPlayer());
 | |
| 	data.stream << quint32(dbiCacheSettings) << qint64(_cacheTotalSizeLimit) << qint32(_cacheTotalTimeLimit) << qint64(_cacheBigFileTotalSizeLimit) << qint32(_cacheBigFileTotalTimeLimit);
 | |
| 	if (!userData.isEmpty()) {
 | |
| 		data.stream << quint32(dbiSessionSettings) << userData;
 | |
| 	}
 | |
| 	data.stream << quint32(dbiRecentStickers) << recentStickers;
 | |
| 
 | |
| 	FileWriteDescriptor file(_settingsKey, _basePath);
 | |
| 	file.writeEncrypted(data, _localKey);
 | |
| }
 | |
| 
 | |
| ReadSettingsContext Account::prepareReadSettingsContext() const {
 | |
| 	return ReadSettingsContext{
 | |
| 		.legacyHasCustomDayBackground = (_legacyBackgroundKeyDay != 0)
 | |
| 	};
 | |
| }
 | |
| 
 | |
| std::unique_ptr<Main::SessionSettings> Account::readSessionSettings() {
 | |
| 	ReadSettingsContext context;
 | |
| 	FileReadDescriptor userSettings;
 | |
| 	if (!ReadEncryptedFile(userSettings, _settingsKey, _basePath, _localKey)) {
 | |
| 		LOG(("App Info: could not read encrypted user settings..."));
 | |
| 
 | |
| 		Local::readOldUserSettings(true, context);
 | |
| 		auto result = applyReadContext(std::move(context));
 | |
| 
 | |
| 		writeSessionSettings(result.get());
 | |
| 
 | |
| 		return result;
 | |
| 	}
 | |
| 
 | |
| 	LOG(("App Info: reading encrypted user settings..."));
 | |
| 	_readingUserSettings = true;
 | |
| 	while (!userSettings.stream.atEnd()) {
 | |
| 		quint32 blockId;
 | |
| 		userSettings.stream >> blockId;
 | |
| 		if (!CheckStreamStatus(userSettings.stream)) {
 | |
| 			_readingUserSettings = false;
 | |
| 			writeSessionSettings();
 | |
| 			return nullptr;
 | |
| 		}
 | |
| 
 | |
| 		if (!ReadSetting(blockId, userSettings.stream, userSettings.version, context)) {
 | |
| 			_readingUserSettings = false;
 | |
| 			writeSessionSettings();
 | |
| 			return nullptr;
 | |
| 		}
 | |
| 	}
 | |
| 	_readingUserSettings = false;
 | |
| 	LOG(("App Info: encrypted user settings read."));
 | |
| 
 | |
| 	auto result = applyReadContext(std::move(context));
 | |
| 	if (context.legacyRead) {
 | |
| 		writeSessionSettings(result.get());
 | |
| 	}
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| std::unique_ptr<Main::SessionSettings> Account::applyReadContext(
 | |
| 		ReadSettingsContext &&context) {
 | |
| 	ApplyReadFallbackConfig(context);
 | |
| 
 | |
| 	if (context.cacheTotalSizeLimit) {
 | |
| 		_cacheTotalSizeLimit = context.cacheTotalSizeLimit;
 | |
| 		_cacheTotalTimeLimit = context.cacheTotalTimeLimit;
 | |
| 		_cacheBigFileTotalSizeLimit = context.cacheBigFileTotalSizeLimit;
 | |
| 		_cacheBigFileTotalTimeLimit = context.cacheBigFileTotalTimeLimit;
 | |
| 
 | |
| 		const auto &normal = Database::Settings();
 | |
| 		Assert(_cacheTotalSizeLimit > normal.maxDataSize);
 | |
| 		Assert(_cacheBigFileTotalSizeLimit > normal.maxDataSize);
 | |
| 	}
 | |
| 
 | |
| 	if (!context.mtpAuthorization.isEmpty()) {
 | |
| 		_owner->setMtpAuthorization(context.mtpAuthorization);
 | |
| 	} else {
 | |
| 		for (auto &key : context.mtpLegacyKeys) {
 | |
| 			_owner->setLegacyMtpKey(std::move(key));
 | |
| 		}
 | |
| 		if (context.mtpLegacyMainDcId) {
 | |
| 			_owner->setMtpMainDcId(context.mtpLegacyMainDcId);
 | |
| 			_owner->setSessionUserId(context.mtpLegacyUserId);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (context.tileRead) {
 | |
| 		Window::Theme::Background()->setTileDayValue(context.tileDay);
 | |
| 		Window::Theme::Background()->setTileNightValue(context.tileNight);
 | |
| 	}
 | |
| 
 | |
| 	return std::move(context.sessionSettingsStorage);
 | |
| }
 | |
| 
 | |
| void Account::writeMtpData() {
 | |
| 	Expects(_localKey != nullptr);
 | |
| 
 | |
| 	const auto serialized = _owner->serializeMtpAuthorization();
 | |
| 	const auto size = sizeof(quint32) + Serialize::bytearraySize(serialized);
 | |
| 
 | |
| 	FileWriteDescriptor mtp(ToFilePart(_dataNameKey), BaseGlobalPath());
 | |
| 	EncryptedDescriptor data(size);
 | |
| 	data.stream << quint32(dbiMtpAuthorization) << serialized;
 | |
| 	mtp.writeEncrypted(data, _localKey);
 | |
| }
 | |
| 
 | |
| void Account::readMtpData() {
 | |
| 	auto context = prepareReadSettingsContext();
 | |
| 
 | |
| 	FileReadDescriptor mtp;
 | |
| 	if (!ReadEncryptedFile(mtp, ToFilePart(_dataNameKey), BaseGlobalPath(), _localKey)) {
 | |
| 		if (_localKey) {
 | |
| 			Local::readOldMtpData(true, context);
 | |
| 			applyReadContext(std::move(context));
 | |
| 			writeMtpData();
 | |
| 		}
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	LOG(("App Info: reading encrypted mtp data..."));
 | |
| 	while (!mtp.stream.atEnd()) {
 | |
| 		quint32 blockId;
 | |
| 		mtp.stream >> blockId;
 | |
| 		if (!CheckStreamStatus(mtp.stream)) {
 | |
| 			return writeMtpData();
 | |
| 		}
 | |
| 
 | |
| 		if (!ReadSetting(blockId, mtp.stream, mtp.version, context)) {
 | |
| 			return writeMtpData();
 | |
| 		}
 | |
| 	}
 | |
| 	applyReadContext(std::move(context));
 | |
| }
 | |
| 
 | |
| void Account::writeMtpConfig() {
 | |
| 	Expects(_localKey != nullptr);
 | |
| 
 | |
| 	const auto serialized = _owner->mtp().config().serialize();
 | |
| 	const auto size = Serialize::bytearraySize(serialized);
 | |
| 
 | |
| 	FileWriteDescriptor file(u"config"_q, _basePath);
 | |
| 	EncryptedDescriptor data(size);
 | |
| 	data.stream << serialized;
 | |
| 	file.writeEncrypted(data, _localKey);
 | |
| }
 | |
| 
 | |
| std::unique_ptr<MTP::Config> Account::readMtpConfig() {
 | |
| 	Expects(_localKey != nullptr);
 | |
| 
 | |
| 	FileReadDescriptor file;
 | |
| 	if (!ReadEncryptedFile(file, "config", _basePath, _localKey)) {
 | |
| 		return nullptr;
 | |
| 	}
 | |
| 
 | |
| 	LOG(("App Info: reading encrypted mtp config..."));
 | |
| 	auto serialized = QByteArray();
 | |
| 	file.stream >> serialized;
 | |
| 	if (!CheckStreamStatus(file.stream)) {
 | |
| 		return nullptr;
 | |
| 	}
 | |
| 	return MTP::Config::FromSerialized(serialized);
 | |
| }
 | |
| 
 | |
| template <typename Callback>
 | |
| void EnumerateDrafts(
 | |
| 		const Data::HistoryDrafts &map,
 | |
| 		Data::Draft *cloudDraft,
 | |
| 		bool supportMode,
 | |
| 		Data::DraftKey replaceKey,
 | |
| 		const MessageDraft &replaceDraft,
 | |
| 		const MessageCursor &replaceCursor,
 | |
| 		Callback &&callback) {
 | |
| 	for (const auto &[key, draft] : map) {
 | |
| 		if (key == Data::DraftKey::Cloud() || key == replaceKey) {
 | |
| 			continue;
 | |
| 		} else if (key == Data::DraftKey::Local()
 | |
| 			&& !supportMode
 | |
| 			&& Data::draftsAreEqual(draft.get(), cloudDraft)) {
 | |
| 			continue;
 | |
| 		}
 | |
| 		callback(
 | |
| 			key,
 | |
| 			draft->msgId,
 | |
| 			draft->textWithTags,
 | |
| 			draft->previewState,
 | |
| 			draft->cursor);
 | |
| 	}
 | |
| 	if (replaceKey
 | |
| 		&& (replaceDraft.msgId
 | |
| 			|| !replaceDraft.textWithTags.text.isEmpty()
 | |
| 			|| replaceCursor != MessageCursor())) {
 | |
| 		callback(
 | |
| 			replaceKey,
 | |
| 			replaceDraft.msgId,
 | |
| 			replaceDraft.textWithTags,
 | |
| 			replaceDraft.previewState,
 | |
| 			replaceCursor);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void Account::writeDrafts(
 | |
| 		not_null<History*> history,
 | |
| 		Data::DraftKey replaceKey,
 | |
| 		MessageDraft replaceDraft) {
 | |
| 	const auto peerId = history->peer->id;
 | |
| 	const auto &map = history->draftsMap();
 | |
| 	const auto cloudIt = map.find(Data::DraftKey::Cloud());
 | |
| 	const auto cloudDraft = (cloudIt != end(map))
 | |
| 		? cloudIt->second.get()
 | |
| 		: nullptr;
 | |
| 	const auto supportMode = _owner->session().supportMode();
 | |
| 	auto count = 0;
 | |
| 	EnumerateDrafts(
 | |
| 		map,
 | |
| 		cloudDraft,
 | |
| 		supportMode,
 | |
| 		replaceKey,
 | |
| 		replaceDraft,
 | |
| 		MessageCursor(),
 | |
| 		[&](auto&&...) { ++count; });
 | |
| 	if (!count) {
 | |
| 		auto i = _draftsMap.find(peerId);
 | |
| 		if (i != _draftsMap.cend()) {
 | |
| 			ClearKey(i->second, _basePath);
 | |
| 			_draftsMap.erase(i);
 | |
| 			writeMapDelayed();
 | |
| 		}
 | |
| 
 | |
| 		_draftsNotReadMap.remove(peerId);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	auto i = _draftsMap.find(peerId);
 | |
| 	if (i == _draftsMap.cend()) {
 | |
| 		i = _draftsMap.emplace(peerId, GenerateKey(_basePath)).first;
 | |
| 		writeMapQueued();
 | |
| 	}
 | |
| 
 | |
| 	auto size = int(sizeof(quint64) * 2 + sizeof(quint32));
 | |
| 	const auto sizeCallback = [&](
 | |
| 			auto&&, // key
 | |
| 			MsgId, // msgId
 | |
| 			const TextWithTags &text,
 | |
| 			Data::PreviewState,
 | |
| 			auto&&) { // cursor
 | |
| 		size += sizeof(qint32) // key
 | |
| 			+ Serialize::stringSize(text.text)
 | |
| 			+ sizeof(quint32) + TextUtilities::SerializeTagsSize(text.tags)
 | |
| 			+ 2 * sizeof(qint32); // msgId, previewState
 | |
| 	};
 | |
| 	EnumerateDrafts(
 | |
| 		map,
 | |
| 		cloudDraft,
 | |
| 		supportMode,
 | |
| 		replaceKey,
 | |
| 		replaceDraft,
 | |
| 		MessageCursor(),
 | |
| 		sizeCallback);
 | |
| 
 | |
| 	EncryptedDescriptor data(size);
 | |
| 	data.stream
 | |
| 		<< quint64(kMultiDraftTag)
 | |
| 		<< SerializePeerId(peerId)
 | |
| 		<< quint32(count);
 | |
| 
 | |
| 	const auto writeCallback = [&](
 | |
| 			const Data::DraftKey &key,
 | |
| 			MsgId msgId,
 | |
| 			const TextWithTags &text,
 | |
| 			Data::PreviewState previewState,
 | |
| 			auto&&) { // cursor
 | |
| 		data.stream
 | |
| 			<< key.serialize()
 | |
| 			<< text.text
 | |
| 			<< TextUtilities::SerializeTags(text.tags)
 | |
| 			<< qint32(msgId)
 | |
| 			<< qint32(previewState);
 | |
| 	};
 | |
| 	EnumerateDrafts(
 | |
| 		map,
 | |
| 		cloudDraft,
 | |
| 		supportMode,
 | |
| 		replaceKey,
 | |
| 		replaceDraft,
 | |
| 		MessageCursor(),
 | |
| 		writeCallback);
 | |
| 
 | |
| 	FileWriteDescriptor file(i->second, _basePath);
 | |
| 	file.writeEncrypted(data, _localKey);
 | |
| 
 | |
| 	_draftsNotReadMap.remove(peerId);
 | |
| }
 | |
| 
 | |
| void Account::writeDraftCursors(
 | |
| 		not_null<History*> history,
 | |
| 		Data::DraftKey replaceKey,
 | |
| 		MessageCursor replaceCursor) {
 | |
| 	const auto peerId = history->peer->id;
 | |
| 	const auto &map = history->draftsMap();
 | |
| 	const auto cloudIt = map.find(Data::DraftKey::Cloud());
 | |
| 	const auto cloudDraft = (cloudIt != end(map))
 | |
| 		? cloudIt->second.get()
 | |
| 		: nullptr;
 | |
| 	const auto supportMode = _owner->session().supportMode();
 | |
| 	auto count = 0;
 | |
| 	EnumerateDrafts(
 | |
| 		map,
 | |
| 		cloudDraft,
 | |
| 		supportMode,
 | |
| 		replaceKey,
 | |
| 		MessageDraft(),
 | |
| 		replaceCursor,
 | |
| 		[&](auto&&...) { ++count; });
 | |
| 	if (!count) {
 | |
| 		clearDraftCursors(peerId);
 | |
| 		return;
 | |
| 	}
 | |
| 	auto i = _draftCursorsMap.find(peerId);
 | |
| 	if (i == _draftCursorsMap.cend()) {
 | |
| 		i = _draftCursorsMap.emplace(peerId, GenerateKey(_basePath)).first;
 | |
| 		writeMapQueued();
 | |
| 	}
 | |
| 
 | |
| 	auto size = int(sizeof(quint64) * 2 + sizeof(quint32) * 4);
 | |
| 
 | |
| 	EncryptedDescriptor data(size);
 | |
| 	data.stream
 | |
| 		<< quint64(kMultiDraftTag)
 | |
| 		<< SerializePeerId(peerId)
 | |
| 		<< quint32(count);
 | |
| 
 | |
| 	const auto writeCallback = [&](
 | |
| 			auto&&, // key
 | |
| 			MsgId, // msgId
 | |
| 			auto&&, // text
 | |
| 			Data::PreviewState,
 | |
| 			const MessageCursor &cursor) { // cursor
 | |
| 		data.stream
 | |
| 			<< qint32(cursor.position)
 | |
| 			<< qint32(cursor.anchor)
 | |
| 			<< qint32(cursor.scroll);
 | |
| 	};
 | |
| 	EnumerateDrafts(
 | |
| 		map,
 | |
| 		cloudDraft,
 | |
| 		supportMode,
 | |
| 		replaceKey,
 | |
| 		MessageDraft(),
 | |
| 		replaceCursor,
 | |
| 		writeCallback);
 | |
| 
 | |
| 	FileWriteDescriptor file(i->second, _basePath);
 | |
| 	file.writeEncrypted(data, _localKey);
 | |
| }
 | |
| 
 | |
| void Account::clearDraftCursors(PeerId peerId) {
 | |
| 	const auto i = _draftCursorsMap.find(peerId);
 | |
| 	if (i != _draftCursorsMap.cend()) {
 | |
| 		ClearKey(i->second, _basePath);
 | |
| 		_draftCursorsMap.erase(i);
 | |
| 		writeMapDelayed();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void Account::readDraftCursors(PeerId peerId, Data::HistoryDrafts &map) {
 | |
| 	const auto j = _draftCursorsMap.find(peerId);
 | |
| 	if (j == _draftCursorsMap.cend()) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	FileReadDescriptor draft;
 | |
| 	if (!ReadEncryptedFile(draft, j->second, _basePath, _localKey)) {
 | |
| 		clearDraftCursors(peerId);
 | |
| 		return;
 | |
| 	}
 | |
| 	quint64 tag = 0;
 | |
| 	draft.stream >> tag;
 | |
| 	if (tag != kMultiDraftTag) {
 | |
| 		readDraftCursorsLegacy(peerId, draft, tag, map);
 | |
| 		return;
 | |
| 	}
 | |
| 	quint64 draftPeerSerialized = 0;
 | |
| 	quint32 count = 0;
 | |
| 	draft.stream >> draftPeerSerialized >> count;
 | |
| 	const auto draftPeer = DeserializePeerId(draftPeerSerialized);
 | |
| 	if (!count || count > 1000 || draftPeer != peerId) {
 | |
| 		clearDraftCursors(peerId);
 | |
| 		return;
 | |
| 	}
 | |
| 	for (auto i = 0; i != count; ++i) {
 | |
| 		qint32 position = 0, anchor = 0, scroll = QFIXED_MAX;
 | |
| 		draft.stream >> position >> anchor >> scroll;
 | |
| 		if (const auto i = map.find(Data::DraftKey::Local()); i != end(map)) {
 | |
| 			i->second->cursor = MessageCursor(position, anchor, scroll);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void Account::readDraftCursorsLegacy(
 | |
| 		PeerId peerId,
 | |
| 		details::FileReadDescriptor &draft,
 | |
| 		quint64 draftPeerSerialized,
 | |
| 		Data::HistoryDrafts &map) {
 | |
| 	qint32 localPosition = 0, localAnchor = 0, localScroll = QFIXED_MAX;
 | |
| 	qint32 editPosition = 0, editAnchor = 0, editScroll = QFIXED_MAX;
 | |
| 	draft.stream >> localPosition >> localAnchor >> localScroll;
 | |
| 	if (!draft.stream.atEnd()) {
 | |
| 		draft.stream >> editPosition >> editAnchor >> editScroll;
 | |
| 	}
 | |
| 
 | |
| 	const auto draftPeer = DeserializePeerId(draftPeerSerialized);
 | |
| 	if (draftPeer != peerId) {
 | |
| 		clearDraftCursors(peerId);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (const auto i = map.find(Data::DraftKey::Local()); i != end(map)) {
 | |
| 		i->second->cursor = MessageCursor(
 | |
| 			localPosition,
 | |
| 			localAnchor,
 | |
| 			localScroll);
 | |
| 	}
 | |
| 	if (const auto i = map.find(Data::DraftKey::LocalEdit()); i != end(map)) {
 | |
| 		i->second->cursor = MessageCursor(
 | |
| 			editPosition,
 | |
| 			editAnchor,
 | |
| 			editScroll);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void Account::readDraftsWithCursors(not_null<History*> history) {
 | |
| 	const auto guard = gsl::finally([&] {
 | |
| 		if (const auto migrated = history->migrateFrom()) {
 | |
| 			readDraftsWithCursors(migrated);
 | |
| 			migrated->clearLocalEditDraft();
 | |
| 			history->takeLocalDraft(migrated);
 | |
| 		}
 | |
| 	});
 | |
| 
 | |
| 	PeerId peerId = history->peer->id;
 | |
| 	if (!_draftsNotReadMap.remove(peerId)) {
 | |
| 		clearDraftCursors(peerId);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	const auto j = _draftsMap.find(peerId);
 | |
| 	if (j == _draftsMap.cend()) {
 | |
| 		clearDraftCursors(peerId);
 | |
| 		return;
 | |
| 	}
 | |
| 	FileReadDescriptor draft;
 | |
| 	if (!ReadEncryptedFile(draft, j->second, _basePath, _localKey)) {
 | |
| 		ClearKey(j->second, _basePath);
 | |
| 		_draftsMap.erase(j);
 | |
| 		clearDraftCursors(peerId);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	quint64 tag = 0;
 | |
| 	draft.stream >> tag;
 | |
| 	if (tag != kMultiDraftTag) {
 | |
| 		readDraftsWithCursorsLegacy(history, draft, tag);
 | |
| 		return;
 | |
| 	}
 | |
| 	quint32 count = 0;
 | |
| 	quint64 draftPeerSerialized = 0;
 | |
| 	draft.stream >> draftPeerSerialized >> count;
 | |
| 	const auto draftPeer = DeserializePeerId(draftPeerSerialized);
 | |
| 	if (!count || count > 1000 || draftPeer != peerId) {
 | |
| 		ClearKey(j->second, _basePath);
 | |
| 		_draftsMap.erase(j);
 | |
| 		clearDraftCursors(peerId);
 | |
| 		return;
 | |
| 	}
 | |
| 	auto map = Data::HistoryDrafts();
 | |
| 	for (auto i = 0; i != count; ++i) {
 | |
| 		TextWithTags data;
 | |
| 		QByteArray tagsSerialized;
 | |
| 		qint32 keyValue = 0, messageId = 0, uncheckedPreviewState = 0;
 | |
| 		draft.stream
 | |
| 			>> keyValue
 | |
| 			>> data.text
 | |
| 			>> tagsSerialized
 | |
| 			>> messageId
 | |
| 			>> uncheckedPreviewState;
 | |
| 		data.tags = TextUtilities::DeserializeTags(
 | |
| 			tagsSerialized,
 | |
| 			data.text.size());
 | |
| 		auto previewState = Data::PreviewState::Allowed;
 | |
| 		switch (static_cast<Data::PreviewState>(uncheckedPreviewState)) {
 | |
| 		case Data::PreviewState::Cancelled:
 | |
| 		case Data::PreviewState::EmptyOnEdit:
 | |
| 			previewState = Data::PreviewState(uncheckedPreviewState);
 | |
| 		}
 | |
| 		const auto key = Data::DraftKey::FromSerialized(keyValue);
 | |
| 		if (key && key != Data::DraftKey::Cloud()) {
 | |
| 			map.emplace(key, std::make_unique<Data::Draft>(
 | |
| 				data,
 | |
| 				messageId,
 | |
| 				MessageCursor(),
 | |
| 				previewState));
 | |
| 		}
 | |
| 	}
 | |
| 	if (draft.stream.status() != QDataStream::Ok) {
 | |
| 		ClearKey(j->second, _basePath);
 | |
| 		_draftsMap.erase(j);
 | |
| 		clearDraftCursors(peerId);
 | |
| 		return;
 | |
| 	}
 | |
| 	readDraftCursors(peerId, map);
 | |
| 	history->setDraftsMap(std::move(map));
 | |
| }
 | |
| 
 | |
| void Account::readDraftsWithCursorsLegacy(
 | |
| 		not_null<History*> history,
 | |
| 		details::FileReadDescriptor &draft,
 | |
| 		quint64 draftPeerSerialized) {
 | |
| 	TextWithTags msgData, editData;
 | |
| 	QByteArray msgTagsSerialized, editTagsSerialized;
 | |
| 	qint32 msgReplyTo = 0, msgPreviewCancelled = 0, editMsgId = 0, editPreviewCancelled = 0;
 | |
| 	draft.stream >> msgData.text;
 | |
| 	if (draft.version >= 9048) {
 | |
| 		draft.stream >> msgTagsSerialized;
 | |
| 	}
 | |
| 	if (draft.version >= 7021) {
 | |
| 		draft.stream >> msgReplyTo;
 | |
| 		if (draft.version >= 8001) {
 | |
| 			draft.stream >> msgPreviewCancelled;
 | |
| 			if (!draft.stream.atEnd()) {
 | |
| 				draft.stream >> editData.text;
 | |
| 				if (draft.version >= 9048) {
 | |
| 					draft.stream >> editTagsSerialized;
 | |
| 				}
 | |
| 				draft.stream >> editMsgId >> editPreviewCancelled;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	const auto peerId = history->peer->id;
 | |
| 	const auto draftPeer = DeserializePeerId(draftPeerSerialized);
 | |
| 	if (draftPeer != peerId) {
 | |
| 		const auto j = _draftsMap.find(peerId);
 | |
| 		if (j != _draftsMap.cend()) {
 | |
| 			ClearKey(j->second, _basePath);
 | |
| 			_draftsMap.erase(j);
 | |
| 		}
 | |
| 		clearDraftCursors(peerId);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	msgData.tags = TextUtilities::DeserializeTags(
 | |
| 		msgTagsSerialized,
 | |
| 		msgData.text.size());
 | |
| 	editData.tags = TextUtilities::DeserializeTags(
 | |
| 		editTagsSerialized,
 | |
| 		editData.text.size());
 | |
| 
 | |
| 	auto map = base::flat_map<Data::DraftKey, std::unique_ptr<Data::Draft>>();
 | |
| 	if (!msgData.text.isEmpty() || msgReplyTo) {
 | |
| 		map.emplace(Data::DraftKey::Local(), std::make_unique<Data::Draft>(
 | |
| 			msgData,
 | |
| 			msgReplyTo,
 | |
| 			MessageCursor(),
 | |
| 			(msgPreviewCancelled
 | |
| 				? Data::PreviewState::Cancelled
 | |
| 				: Data::PreviewState::Allowed)));
 | |
| 	}
 | |
| 	if (editMsgId) {
 | |
| 		map.emplace(Data::DraftKey::LocalEdit(), std::make_unique<Data::Draft>(
 | |
| 			editData,
 | |
| 			editMsgId,
 | |
| 			MessageCursor(),
 | |
| 			(editPreviewCancelled
 | |
| 				? Data::PreviewState::Cancelled
 | |
| 				: Data::PreviewState::Allowed)));
 | |
| 	}
 | |
| 	readDraftCursors(peerId, map);
 | |
| 	history->setDraftsMap(std::move(map));
 | |
| }
 | |
| 
 | |
| bool Account::hasDraftCursors(PeerId peer) {
 | |
| 	return _draftCursorsMap.contains(peer);
 | |
| }
 | |
| 
 | |
| bool Account::hasDraft(PeerId peer) {
 | |
| 	return _draftsMap.contains(peer);
 | |
| }
 | |
| 
 | |
| void Account::writeFileLocation(MediaKey location, const Core::FileLocation &local) {
 | |
| 	if (local.fname.isEmpty()) {
 | |
| 		return;
 | |
| 	}
 | |
| 	if (!local.inMediaCache()) {
 | |
| 		const auto aliasIt = _fileLocationAliases.constFind(location);
 | |
| 		if (aliasIt != _fileLocationAliases.cend()) {
 | |
| 			location = aliasIt.value();
 | |
| 		}
 | |
| 
 | |
| 		const auto i = _fileLocationPairs.find(local.fname);
 | |
| 		if (i != _fileLocationPairs.cend()) {
 | |
| 			if (i.value().second == local) {
 | |
| 				if (i.value().first != location) {
 | |
| 					_fileLocationAliases.insert(location, i.value().first);
 | |
| 					writeLocationsQueued();
 | |
| 				}
 | |
| 				return;
 | |
| 			}
 | |
| 			if (i.value().first != location) {
 | |
| 				for (auto j = _fileLocations.find(i.value().first), e = _fileLocations.end(); (j != e) && (j.key() == i.value().first); ++j) {
 | |
| 					if (j.value() == i.value().second) {
 | |
| 						_fileLocations.erase(j);
 | |
| 						break;
 | |
| 					}
 | |
| 				}
 | |
| 				_fileLocationPairs.erase(i);
 | |
| 			}
 | |
| 		}
 | |
| 		_fileLocationPairs.insert(local.fname, { location, local });
 | |
| 	} else {
 | |
| 		for (auto i = _fileLocations.find(location); (i != _fileLocations.end()) && (i.key() == location);) {
 | |
| 			if (i.value().inMediaCache() || i.value().check()) {
 | |
| 				return;
 | |
| 			}
 | |
| 			i = _fileLocations.erase(i);
 | |
| 		}
 | |
| 	}
 | |
| 	_fileLocations.insert(location, local);
 | |
| 	writeLocationsQueued();
 | |
| }
 | |
| 
 | |
| void Account::removeFileLocation(MediaKey location) {
 | |
| 	auto i = _fileLocations.find(location);
 | |
| 	if (i == _fileLocations.end()) {
 | |
| 		return;
 | |
| 	}
 | |
| 	while (i != _fileLocations.end() && (i.key() == location)) {
 | |
| 		i = _fileLocations.erase(i);
 | |
| 	}
 | |
| 	writeLocationsQueued();
 | |
| }
 | |
| 
 | |
| Core::FileLocation Account::readFileLocation(MediaKey location) {
 | |
| 	const auto aliasIt = _fileLocationAliases.constFind(location);
 | |
| 	if (aliasIt != _fileLocationAliases.cend()) {
 | |
| 		location = aliasIt.value();
 | |
| 	}
 | |
| 
 | |
| 	for (auto i = _fileLocations.find(location); (i != _fileLocations.end()) && (i.key() == location);) {
 | |
| 		if (!i.value().inMediaCache() && !i.value().check()) {
 | |
| 			_fileLocationPairs.remove(i.value().fname);
 | |
| 			i = _fileLocations.erase(i);
 | |
| 			writeLocationsDelayed();
 | |
| 			continue;
 | |
| 		}
 | |
| 		return i.value();
 | |
| 	}
 | |
| 	return Core::FileLocation();
 | |
| }
 | |
| 
 | |
| EncryptionKey Account::cacheKey() const {
 | |
| 	Expects(_localKey != nullptr);
 | |
| 
 | |
| 	return EncryptionKey(bytes::make_vector(_localKey->data()));
 | |
| }
 | |
| 
 | |
| EncryptionKey Account::cacheBigFileKey() const {
 | |
| 	return cacheKey();
 | |
| }
 | |
| 
 | |
| QString Account::cachePath() const {
 | |
| 	Expects(!_databasePath.isEmpty());
 | |
| 
 | |
| 	return _databasePath + "cache";
 | |
| }
 | |
| 
 | |
| Cache::Database::Settings Account::cacheSettings() const {
 | |
| 	auto result = Cache::Database::Settings();
 | |
| 	result.clearOnWrongKey = true;
 | |
| 	result.totalSizeLimit = _cacheTotalSizeLimit;
 | |
| 	result.totalTimeLimit = _cacheTotalTimeLimit;
 | |
| 	result.maxDataSize = kMaxFileInMemory;
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| void Account::updateCacheSettings(
 | |
| 		Cache::Database::SettingsUpdate &update,
 | |
| 		Cache::Database::SettingsUpdate &updateBig) {
 | |
| 	Expects(update.totalSizeLimit > Database::Settings().maxDataSize);
 | |
| 	Expects(update.totalTimeLimit >= 0);
 | |
| 	Expects(updateBig.totalSizeLimit > Database::Settings().maxDataSize);
 | |
| 	Expects(updateBig.totalTimeLimit >= 0);
 | |
| 
 | |
| 	if (_cacheTotalSizeLimit == update.totalSizeLimit
 | |
| 		&& _cacheTotalTimeLimit == update.totalTimeLimit
 | |
| 		&& _cacheBigFileTotalSizeLimit == updateBig.totalSizeLimit
 | |
| 		&& _cacheBigFileTotalTimeLimit == updateBig.totalTimeLimit) {
 | |
| 		return;
 | |
| 	}
 | |
| 	_cacheTotalSizeLimit = update.totalSizeLimit;
 | |
| 	_cacheTotalTimeLimit = update.totalTimeLimit;
 | |
| 	_cacheBigFileTotalSizeLimit = updateBig.totalSizeLimit;
 | |
| 	_cacheBigFileTotalTimeLimit = updateBig.totalTimeLimit;
 | |
| 	writeSessionSettings();
 | |
| }
 | |
| 
 | |
| QString Account::cacheBigFilePath() const {
 | |
| 	Expects(!_databasePath.isEmpty());
 | |
| 
 | |
| 	return _databasePath + "media_cache";
 | |
| }
 | |
| 
 | |
| Cache::Database::Settings Account::cacheBigFileSettings() const {
 | |
| 	auto result = Cache::Database::Settings();
 | |
| 	result.clearOnWrongKey = true;
 | |
| 	result.totalSizeLimit = _cacheBigFileTotalSizeLimit;
 | |
| 	result.totalTimeLimit = _cacheBigFileTotalTimeLimit;
 | |
| 	result.maxDataSize = kMaxFileInMemory;
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| void Account::writeStickerSet(
 | |
| 		QDataStream &stream,
 | |
| 		const Data::StickersSet &set) {
 | |
| 	using SetFlag = Data::StickersSetFlag;
 | |
| 	const auto writeInfo = [&](int count) {
 | |
| 		stream
 | |
| 			<< quint64(set.id)
 | |
| 			<< quint64(set.access)
 | |
| 			<< set.title
 | |
| 			<< set.shortName
 | |
| 			<< qint32(count)
 | |
| 			<< qint32(set.hash)
 | |
| 			<< qint32(set.flags)
 | |
| 			<< qint32(set.installDate);
 | |
| 		Serialize::writeImageLocation(stream, set.thumbnailLocation());
 | |
| 	};
 | |
| 	if (set.flags & SetFlag::NotLoaded) {
 | |
| 		writeInfo(-set.count);
 | |
| 		return;
 | |
| 	} else if (set.stickers.isEmpty()) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	writeInfo(set.stickers.size());
 | |
| 	for (const auto &sticker : set.stickers) {
 | |
| 		Serialize::Document::writeToStream(stream, sticker);
 | |
| 	}
 | |
| 	stream << qint32(set.dates.size());
 | |
| 	if (!set.dates.empty()) {
 | |
| 		Assert(set.dates.size() == set.stickers.size());
 | |
| 		for (const auto date : set.dates) {
 | |
| 			stream << qint32(date);
 | |
| 		}
 | |
| 	}
 | |
| 	stream << qint32(set.emoji.size());
 | |
| 	for (auto j = set.emoji.cbegin(), e = set.emoji.cend(); j != e; ++j) {
 | |
| 		stream << j.key()->id() << qint32(j->size());
 | |
| 		for (const auto sticker : *j) {
 | |
| 			stream << quint64(sticker->id);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // In generic method _writeStickerSets() we look through all the sets and call a
 | |
| // callback on each set to see, if we write it, skip it or abort the whole write.
 | |
| enum class StickerSetCheckResult {
 | |
| 	Write,
 | |
| 	Skip,
 | |
| 	Abort,
 | |
| };
 | |
| 
 | |
| // CheckSet is a functor on Data::StickersSet, which returns a StickerSetCheckResult.
 | |
| template <typename CheckSet>
 | |
| void Account::writeStickerSets(
 | |
| 		FileKey &stickersKey,
 | |
| 		CheckSet checkSet,
 | |
| 		const Data::StickersSetsOrder &order) {
 | |
| 	using SetFlag = Data::StickersSetFlag;
 | |
| 
 | |
| 	const auto &sets = _owner->session().data().stickers().sets();
 | |
| 	if (sets.empty()) {
 | |
| 		if (stickersKey) {
 | |
| 			ClearKey(stickersKey, _basePath);
 | |
| 			stickersKey = 0;
 | |
| 			writeMapDelayed();
 | |
| 		}
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	// versionTag + version + count
 | |
| 	quint32 size = sizeof(quint32) + sizeof(qint32) + sizeof(qint32);
 | |
| 
 | |
| 	int32 setsCount = 0;
 | |
| 	for (const auto &[id, set] : sets) {
 | |
| 		const auto raw = set.get();
 | |
| 		auto result = checkSet(*raw);
 | |
| 		if (result == StickerSetCheckResult::Abort) {
 | |
| 			return;
 | |
| 		} else if (result == StickerSetCheckResult::Skip) {
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		// id + access + title + shortName + stickersCount + hash + flags + installDate
 | |
| 		size += sizeof(quint64) * 2
 | |
| 			+ Serialize::stringSize(raw->title)
 | |
| 			+ Serialize::stringSize(raw->shortName)
 | |
| 			+ sizeof(qint32) * 4
 | |
| 			+ Serialize::imageLocationSize(raw->thumbnailLocation());
 | |
| 		if (raw->flags & SetFlag::NotLoaded) {
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		for (const auto sticker : std::as_const(raw->stickers)) {
 | |
| 			size += Serialize::Document::sizeInStream(sticker);
 | |
| 		}
 | |
| 
 | |
| 		size += sizeof(qint32); // datesCount
 | |
| 		if (!raw->dates.empty()) {
 | |
| 			Assert(raw->stickers.size() == raw->dates.size());
 | |
| 			size += raw->dates.size() * sizeof(qint32);
 | |
| 		}
 | |
| 
 | |
| 		size += sizeof(qint32); // emojiCount
 | |
| 		for (auto j = raw->emoji.cbegin(), e = raw->emoji.cend(); j != e; ++j) {
 | |
| 			size += Serialize::stringSize(j.key()->id()) + sizeof(qint32) + (j->size() * sizeof(quint64));
 | |
| 		}
 | |
| 
 | |
| 		++setsCount;
 | |
| 	}
 | |
| 	if (!setsCount && order.isEmpty()) {
 | |
| 		if (stickersKey) {
 | |
| 			ClearKey(stickersKey, _basePath);
 | |
| 			stickersKey = 0;
 | |
| 			writeMapDelayed();
 | |
| 		}
 | |
| 		return;
 | |
| 	}
 | |
| 	size += sizeof(qint32) + (order.size() * sizeof(quint64));
 | |
| 
 | |
| 	if (!stickersKey) {
 | |
| 		stickersKey = GenerateKey(_basePath);
 | |
| 		writeMapQueued();
 | |
| 	}
 | |
| 	EncryptedDescriptor data(size);
 | |
| 	data.stream
 | |
| 		<< quint32(kStickersVersionTag)
 | |
| 		<< qint32(kStickersSerializeVersion)
 | |
| 		<< qint32(setsCount);
 | |
| 	for (const auto &[id, set] : sets) {
 | |
| 		auto result = checkSet(*set);
 | |
| 		if (result == StickerSetCheckResult::Abort) {
 | |
| 			return;
 | |
| 		} else if (result == StickerSetCheckResult::Skip) {
 | |
| 			continue;
 | |
| 		}
 | |
| 		writeStickerSet(data.stream, *set);
 | |
| 	}
 | |
| 	data.stream << order;
 | |
| 
 | |
| 	FileWriteDescriptor file(stickersKey, _basePath);
 | |
| 	file.writeEncrypted(data, _localKey);
 | |
| }
 | |
| 
 | |
| void Account::readStickerSets(
 | |
| 		FileKey &stickersKey,
 | |
| 		Data::StickersSetsOrder *outOrder,
 | |
| 		Data::StickersSetFlags readingFlags) {
 | |
| 	using SetFlag = Data::StickersSetFlag;
 | |
| 
 | |
| 	FileReadDescriptor stickers;
 | |
| 	if (!ReadEncryptedFile(stickers, stickersKey, _basePath, _localKey)) {
 | |
| 		ClearKey(stickersKey, _basePath);
 | |
| 		stickersKey = 0;
 | |
| 		writeMapDelayed();
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	const auto failed = [&] {
 | |
| 		ClearKey(stickersKey, _basePath);
 | |
| 		stickersKey = 0;
 | |
| 	};
 | |
| 
 | |
| 	auto &sets = _owner->session().data().stickers().setsRef();
 | |
| 	if (outOrder) outOrder->clear();
 | |
| 
 | |
| 	quint32 versionTag = 0;
 | |
| 	qint32 version = 0;
 | |
| 	stickers.stream >> versionTag >> version;
 | |
| 	if (versionTag != kStickersVersionTag
 | |
| 		|| version != kStickersSerializeVersion) {
 | |
| 		// Old data, without sticker set thumbnails.
 | |
| 		return failed();
 | |
| 	}
 | |
| 	qint32 count = 0;
 | |
| 	stickers.stream >> count;
 | |
| 	if (!CheckStreamStatus(stickers.stream)
 | |
| 		|| (count < 0)
 | |
| 		|| (count > kMaxSavedStickerSetsCount)) {
 | |
| 		return failed();
 | |
| 	}
 | |
| 	for (auto i = 0; i != count; ++i) {
 | |
| 		quint64 setId = 0, setAccess = 0;
 | |
| 		QString setTitle, setShortName;
 | |
| 		qint32 scnt = 0;
 | |
| 		qint32 setInstallDate = 0;
 | |
| 		qint32 setHash = 0;
 | |
| 		Data::StickersSetFlags setFlags = 0;
 | |
| 		qint32 setFlagsValue = 0;
 | |
| 		ImageLocation setThumbnail;
 | |
| 
 | |
| 		stickers.stream
 | |
| 			>> setId
 | |
| 			>> setAccess
 | |
| 			>> setTitle
 | |
| 			>> setShortName
 | |
| 			>> scnt
 | |
| 			>> setHash
 | |
| 			>> setFlagsValue
 | |
| 			>> setInstallDate;
 | |
| 		const auto thumbnail = Serialize::readImageLocation(
 | |
| 			stickers.version,
 | |
| 			stickers.stream);
 | |
| 		if (!thumbnail || !CheckStreamStatus(stickers.stream)) {
 | |
| 			return failed();
 | |
| 		} else if (thumbnail->valid() && thumbnail->isLegacy()) {
 | |
| 			// No thumb_version information in legacy location.
 | |
| 			return failed();
 | |
| 		} else {
 | |
| 			setThumbnail = *thumbnail;
 | |
| 		}
 | |
| 
 | |
| 		if (stickers.version >= 2008007) {
 | |
| 			setFlags = Data::StickersSetFlags::from_raw(setFlagsValue);
 | |
| 		} else {
 | |
| 			using Saved = MTPDstickerSet::Flag;
 | |
| 			using Flag = SetFlag;
 | |
| 			struct Conversion {
 | |
| 				Saved saved;
 | |
| 				Flag flag;
 | |
| 			};
 | |
| 			const auto conversions = {
 | |
| 				Conversion{ Saved::f_archived, Flag::Archived },
 | |
| 				Conversion{ Saved::f_installed_date, Flag::Installed },
 | |
| 				Conversion{ Saved::f_masks, Flag::Masks },
 | |
| 				Conversion{ Saved::f_official, Flag::Official },
 | |
| 				Conversion{ Saved(1U << 30), Flag::NotLoaded },
 | |
| 				Conversion{ Saved(1U << 29), Flag::Featured },
 | |
| 				Conversion{ Saved(1U << 28), Flag::Unread },
 | |
| 				Conversion{ Saved(1U << 27), Flag::Special },
 | |
| 			};
 | |
| 			auto flagsSet = Flag() | 0;
 | |
| 			for (const auto &conversion : conversions) {
 | |
| 				if (setFlagsValue & int(conversion.saved)) {
 | |
| 					flagsSet |= conversion.flag;
 | |
| 				}
 | |
| 			}
 | |
| 			setFlags = flagsSet;
 | |
| 		}
 | |
| 
 | |
| 		if (setId == Data::Stickers::DefaultSetId) {
 | |
| 			setTitle = tr::lng_stickers_default_set(tr::now);
 | |
| 			setFlags |= SetFlag::Official | SetFlag::Special;
 | |
| 		} else if (setId == Data::Stickers::CustomSetId) {
 | |
| 			setTitle = qsl("Custom stickers");
 | |
| 			setFlags |= SetFlag::Special;
 | |
| 		} else if ((setId == Data::Stickers::CloudRecentSetId)
 | |
| 				|| (setId == Data::Stickers::CloudRecentAttachedSetId)) {
 | |
| 			setTitle = tr::lng_recent_stickers(tr::now);
 | |
| 			setFlags |= SetFlag::Special;
 | |
| 		} else if (setId == Data::Stickers::FavedSetId) {
 | |
| 			setTitle = Lang::Hard::FavedSetTitle();
 | |
| 			setFlags |= SetFlag::Special;
 | |
| 		} else if (!setId) {
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		auto it = sets.find(setId);
 | |
| 		if (it == sets.cend()) {
 | |
| 			// We will set this flags from order lists when reading those stickers.
 | |
| 			setFlags &= ~(SetFlag::Installed | SetFlag::Featured);
 | |
| 			it = sets.emplace(setId, std::make_unique<Data::StickersSet>(
 | |
| 				&_owner->session().data(),
 | |
| 				setId,
 | |
| 				setAccess,
 | |
| 				setTitle,
 | |
| 				setShortName,
 | |
| 				0,
 | |
| 				setHash,
 | |
| 				setFlags,
 | |
| 				setInstallDate)).first;
 | |
| 			it->second->setThumbnail(
 | |
| 				ImageWithLocation{ .location = setThumbnail });
 | |
| 		}
 | |
| 		const auto set = it->second.get();
 | |
| 		const auto inputSet = set->identifier();
 | |
| 		const auto fillStickers = set->stickers.isEmpty();
 | |
| 
 | |
| 		if (scnt < 0) { // disabled not loaded set
 | |
| 			if (!set->count || fillStickers) {
 | |
| 				set->count = -scnt;
 | |
| 			}
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		if (fillStickers) {
 | |
| 			set->stickers.reserve(scnt);
 | |
| 			set->count = 0;
 | |
| 		}
 | |
| 
 | |
| 		Serialize::Document::StickerSetInfo info(setId, setAccess, setShortName);
 | |
| 		base::flat_set<DocumentId> read;
 | |
| 		for (int32 j = 0; j < scnt; ++j) {
 | |
| 			auto document = Serialize::Document::readStickerFromStream(
 | |
| 				&_owner->session(),
 | |
| 				stickers.version,
 | |
| 				stickers.stream, info);
 | |
| 			if (!CheckStreamStatus(stickers.stream)) {
 | |
| 				return failed();
 | |
| 			} else if (!document
 | |
| 				|| !document->sticker()
 | |
| 				|| read.contains(document->id)) {
 | |
| 				continue;
 | |
| 			}
 | |
| 			read.emplace(document->id);
 | |
| 			if (fillStickers) {
 | |
| 				set->stickers.push_back(document);
 | |
| 				if (!(set->flags & SetFlag::Special)) {
 | |
| 					if (!document->sticker()->set.id) {
 | |
| 						document->sticker()->set = inputSet;
 | |
| 					}
 | |
| 				}
 | |
| 				++set->count;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		qint32 datesCount = 0;
 | |
| 		stickers.stream >> datesCount;
 | |
| 		if (datesCount > 0) {
 | |
| 			if (datesCount != scnt) {
 | |
| 				return failed();
 | |
| 			}
 | |
| 			const auto fillDates =
 | |
| 				((set->id == Data::Stickers::CloudRecentSetId)
 | |
| 					|| (set->id == Data::Stickers::CloudRecentAttachedSetId))
 | |
| 				&& (set->stickers.size() == datesCount);
 | |
| 			if (fillDates) {
 | |
| 				set->dates.clear();
 | |
| 				set->dates.reserve(datesCount);
 | |
| 			}
 | |
| 			for (auto i = 0; i != datesCount; ++i) {
 | |
| 				qint32 date = 0;
 | |
| 				stickers.stream >> date;
 | |
| 				if (fillDates) {
 | |
| 					set->dates.push_back(TimeId(date));
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		qint32 emojiCount = 0;
 | |
| 		stickers.stream >> emojiCount;
 | |
| 		if (!CheckStreamStatus(stickers.stream) || emojiCount < 0) {
 | |
| 			return failed();
 | |
| 		}
 | |
| 		for (int32 j = 0; j < emojiCount; ++j) {
 | |
| 			QString emojiString;
 | |
| 			qint32 stickersCount;
 | |
| 			stickers.stream >> emojiString >> stickersCount;
 | |
| 			Data::StickersPack pack;
 | |
| 			pack.reserve(stickersCount);
 | |
| 			for (int32 k = 0; k < stickersCount; ++k) {
 | |
| 				quint64 id;
 | |
| 				stickers.stream >> id;
 | |
| 				const auto doc = _owner->session().data().document(id);
 | |
| 				if (!doc->sticker()) continue;
 | |
| 
 | |
| 				pack.push_back(doc);
 | |
| 			}
 | |
| 			if (fillStickers) {
 | |
| 				if (auto emoji = Ui::Emoji::Find(emojiString)) {
 | |
| 					emoji = emoji->original();
 | |
| 					set->emoji.insert(emoji, pack);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Read orders of installed and featured stickers.
 | |
| 	if (outOrder) {
 | |
| 		auto outOrderCount = quint32();
 | |
| 		stickers.stream >> outOrderCount;
 | |
| 		if (!CheckStreamStatus(stickers.stream) || outOrderCount > 1000) {
 | |
| 			return failed();
 | |
| 		}
 | |
| 		outOrder->reserve(outOrderCount);
 | |
| 		for (auto i = 0; i != outOrderCount; ++i) {
 | |
| 			auto value = uint64();
 | |
| 			stickers.stream >> value;
 | |
| 			if (!CheckStreamStatus(stickers.stream)) {
 | |
| 				outOrder->clear();
 | |
| 				return failed();
 | |
| 			}
 | |
| 			outOrder->push_back(value);
 | |
| 		}
 | |
| 	}
 | |
| 	if (!CheckStreamStatus(stickers.stream)) {
 | |
| 		return failed();
 | |
| 	}
 | |
| 
 | |
| 	// Set flags that we dropped above from the order.
 | |
| 	if (readingFlags && outOrder) {
 | |
| 		for (const auto setId : std::as_const(*outOrder)) {
 | |
| 			auto it = sets.find(setId);
 | |
| 			if (it != sets.cend()) {
 | |
| 				const auto set = it->second.get();
 | |
| 				set->flags |= readingFlags;
 | |
| 				if ((readingFlags == SetFlag::Installed)
 | |
| 					&& !set->installDate) {
 | |
| 					set->installDate = kDefaultStickerInstallDate;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void Account::writeInstalledStickers() {
 | |
| 	using SetFlag = Data::StickersSetFlag;
 | |
| 
 | |
| 	writeStickerSets(_installedStickersKey, [](const Data::StickersSet &set) {
 | |
| 		if (set.id == Data::Stickers::CloudRecentSetId
 | |
| 			|| set.id == Data::Stickers::FavedSetId
 | |
| 			|| set.id == Data::Stickers::CloudRecentAttachedSetId) {
 | |
| 			// separate files for them
 | |
| 			return StickerSetCheckResult::Skip;
 | |
| 		} else if (set.flags & SetFlag::Special) {
 | |
| 			if (set.stickers.isEmpty()) { // all other special are "installed"
 | |
| 				return StickerSetCheckResult::Skip;
 | |
| 			}
 | |
| 		} else if (!(set.flags & SetFlag::Installed)
 | |
| 			|| (set.flags & SetFlag::Archived)) {
 | |
| 			return StickerSetCheckResult::Skip;
 | |
| 		} else if (set.flags & SetFlag::Masks) {
 | |
| 			return StickerSetCheckResult::Skip;
 | |
| 		} else if (set.flags & SetFlag::NotLoaded) {
 | |
| 			// waiting to receive
 | |
| 			return StickerSetCheckResult::Abort;
 | |
| 		} else if (set.stickers.isEmpty()) {
 | |
| 			return StickerSetCheckResult::Skip;
 | |
| 		}
 | |
| 		return StickerSetCheckResult::Write;
 | |
| 	}, _owner->session().data().stickers().setsOrder());
 | |
| }
 | |
| 
 | |
| void Account::writeFeaturedStickers() {
 | |
| 	using SetFlag = Data::StickersSetFlag;
 | |
| 
 | |
| 	writeStickerSets(_featuredStickersKey, [](const Data::StickersSet &set) {
 | |
| 		if (set.id == Data::Stickers::CloudRecentSetId
 | |
| 			|| set.id == Data::Stickers::FavedSetId
 | |
| 			|| set.id == Data::Stickers::CloudRecentAttachedSetId) {
 | |
| 			// separate files for them
 | |
| 			return StickerSetCheckResult::Skip;
 | |
| 		} else if (set.flags & SetFlag::Special) {
 | |
| 			return StickerSetCheckResult::Skip;
 | |
| 		} else if (!(set.flags & SetFlag::Featured)) {
 | |
| 			return StickerSetCheckResult::Skip;
 | |
| 		} else if (set.flags & SetFlag::NotLoaded) { // waiting to receive
 | |
| 			return StickerSetCheckResult::Abort;
 | |
| 		} else if (set.stickers.isEmpty()) {
 | |
| 			return StickerSetCheckResult::Skip;
 | |
| 		}
 | |
| 		return StickerSetCheckResult::Write;
 | |
| 	}, _owner->session().data().stickers().featuredSetsOrder());
 | |
| }
 | |
| 
 | |
| void Account::writeRecentStickers() {
 | |
| 	writeStickerSets(_recentStickersKey, [](const Data::StickersSet &set) {
 | |
| 		if (set.id != Data::Stickers::CloudRecentSetId
 | |
| 			|| set.stickers.isEmpty()) {
 | |
| 			return StickerSetCheckResult::Skip;
 | |
| 		}
 | |
| 		return StickerSetCheckResult::Write;
 | |
| 	}, Data::StickersSetsOrder());
 | |
| }
 | |
| 
 | |
| void Account::writeFavedStickers() {
 | |
| 	writeStickerSets(_favedStickersKey, [](const Data::StickersSet &set) {
 | |
| 		if (set.id != Data::Stickers::FavedSetId || set.stickers.isEmpty()) {
 | |
| 			return StickerSetCheckResult::Skip;
 | |
| 		}
 | |
| 		return StickerSetCheckResult::Write;
 | |
| 	}, Data::StickersSetsOrder());
 | |
| }
 | |
| 
 | |
| void Account::writeArchivedStickers() {
 | |
| 	using SetFlag = Data::StickersSetFlag;
 | |
| 
 | |
| 	writeStickerSets(_archivedStickersKey, [](const Data::StickersSet &set) {
 | |
| 		if (set.flags & SetFlag::Masks) {
 | |
| 			return StickerSetCheckResult::Skip;
 | |
| 		}
 | |
| 		if (!(set.flags & SetFlag::Archived)
 | |
| 			|| set.stickers.isEmpty()) {
 | |
| 			return StickerSetCheckResult::Skip;
 | |
| 		}
 | |
| 		return StickerSetCheckResult::Write;
 | |
| 	}, _owner->session().data().stickers().archivedSetsOrder());
 | |
| }
 | |
| 
 | |
| void Account::writeArchivedMasks() {
 | |
| 	using SetFlag = Data::StickersSetFlag;
 | |
| 
 | |
| 	writeStickerSets(_archivedStickersKey, [](const Data::StickersSet &set) {
 | |
| 		if (!(set.flags & SetFlag::Masks)) {
 | |
| 			return StickerSetCheckResult::Skip;
 | |
| 		}
 | |
| 		if (!(set.flags & SetFlag::Archived) || set.stickers.isEmpty()) {
 | |
| 			return StickerSetCheckResult::Skip;
 | |
| 		}
 | |
| 		return StickerSetCheckResult::Write;
 | |
| 	}, _owner->session().data().stickers().archivedMaskSetsOrder());
 | |
| }
 | |
| 
 | |
| void Account::writeInstalledMasks() {
 | |
| 	using SetFlag = Data::StickersSetFlag;
 | |
| 
 | |
| 	writeStickerSets(_installedMasksKey, [](const Data::StickersSet &set) {
 | |
| 		if (!(set.flags & SetFlag::Masks) || set.stickers.isEmpty()) {
 | |
| 			return StickerSetCheckResult::Skip;
 | |
| 		}
 | |
| 		return StickerSetCheckResult::Write;
 | |
| 	}, _owner->session().data().stickers().maskSetsOrder());
 | |
| }
 | |
| 
 | |
| void Account::writeRecentMasks() {
 | |
| 	writeStickerSets(_recentMasksKey, [](const Data::StickersSet &set) {
 | |
| 		if (set.id != Data::Stickers::CloudRecentAttachedSetId
 | |
| 			|| set.stickers.isEmpty()) {
 | |
| 			return StickerSetCheckResult::Skip;
 | |
| 		}
 | |
| 		return StickerSetCheckResult::Write;
 | |
| 	}, Data::StickersSetsOrder());
 | |
| }
 | |
| 
 | |
| void Account::importOldRecentStickers() {
 | |
| 	using SetFlag = Data::StickersSetFlag;
 | |
| 
 | |
| 	if (!_recentStickersKeyOld) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	FileReadDescriptor stickers;
 | |
| 	if (!ReadEncryptedFile(stickers, _recentStickersKeyOld, _basePath, _localKey)) {
 | |
| 		ClearKey(_recentStickersKeyOld, _basePath);
 | |
| 		_recentStickersKeyOld = 0;
 | |
| 		writeMapDelayed();
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	auto &sets = _owner->session().data().stickers().setsRef();
 | |
| 	sets.clear();
 | |
| 
 | |
| 	auto &order = _owner->session().data().stickers().setsOrderRef();
 | |
| 	order.clear();
 | |
| 
 | |
| 	auto &recent = cRefRecentStickers();
 | |
| 	recent.clear();
 | |
| 
 | |
| 	const auto def = sets.emplace(
 | |
| 		Data::Stickers::DefaultSetId,
 | |
| 		std::make_unique<Data::StickersSet>(
 | |
| 			&_owner->session().data(),
 | |
| 			Data::Stickers::DefaultSetId,
 | |
| 			uint64(0),
 | |
| 			tr::lng_stickers_default_set(tr::now),
 | |
| 			QString(),
 | |
| 			0, // count
 | |
| 			0, // hash
 | |
| 			(SetFlag::Official | SetFlag::Installed | SetFlag::Special),
 | |
| 			kDefaultStickerInstallDate)).first->second.get();
 | |
| 	const auto custom = sets.emplace(
 | |
| 		Data::Stickers::CustomSetId,
 | |
| 		std::make_unique<Data::StickersSet>(
 | |
| 			&_owner->session().data(),
 | |
| 			Data::Stickers::CustomSetId,
 | |
| 			uint64(0),
 | |
| 			qsl("Custom stickers"),
 | |
| 			QString(),
 | |
| 			0, // count
 | |
| 			0, // hash
 | |
| 			(SetFlag::Installed | SetFlag::Special),
 | |
| 			kDefaultStickerInstallDate)).first->second.get();
 | |
| 
 | |
| 	QMap<uint64, bool> read;
 | |
| 	while (!stickers.stream.atEnd()) {
 | |
| 		quint64 id, access;
 | |
| 		QString name, mime, alt;
 | |
| 		qint32 date, dc, size, width, height, type;
 | |
| 		qint16 value;
 | |
| 		stickers.stream >> id >> value >> access >> date >> name >> mime >> dc >> size >> width >> height >> type;
 | |
| 		if (stickers.version >= 7021) {
 | |
| 			stickers.stream >> alt;
 | |
| 		}
 | |
| 		if (!value || read.contains(id)) continue;
 | |
| 		read.insert(id, true);
 | |
| 
 | |
| 		QVector<MTPDocumentAttribute> attributes;
 | |
| 		if (!name.isEmpty()) attributes.push_back(MTP_documentAttributeFilename(MTP_string(name)));
 | |
| 		if (type == AnimatedDocument) {
 | |
| 			attributes.push_back(MTP_documentAttributeAnimated());
 | |
| 		} else if (type == StickerDocument) {
 | |
| 			attributes.push_back(MTP_documentAttributeSticker(MTP_flags(0), MTP_string(alt), MTP_inputStickerSetEmpty(), MTPMaskCoords()));
 | |
| 		}
 | |
| 		if (width > 0 && height > 0) {
 | |
| 			attributes.push_back(MTP_documentAttributeImageSize(MTP_int(width), MTP_int(height)));
 | |
| 		}
 | |
| 
 | |
| 		const auto doc = _owner->session().data().document(
 | |
| 			id,
 | |
| 			access,
 | |
| 			QByteArray(),
 | |
| 			date,
 | |
| 			attributes,
 | |
| 			mime,
 | |
| 			InlineImageLocation(),
 | |
| 			ImageWithLocation(),
 | |
| 			ImageWithLocation(),
 | |
| 			dc,
 | |
| 			size);
 | |
| 		if (!doc->sticker()) {
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		if (value > 0) {
 | |
| 			def->stickers.push_back(doc);
 | |
| 			++def->count;
 | |
| 		} else {
 | |
| 			custom->stickers.push_back(doc);
 | |
| 			++custom->count;
 | |
| 		}
 | |
| 		if (qAbs(value) > 1
 | |
| 			&& (recent.size()
 | |
| 				< _owner->session().serverConfig().stickersRecentLimit)) {
 | |
| 			recent.push_back(qMakePair(doc, qAbs(value)));
 | |
| 		}
 | |
| 	}
 | |
| 	if (def->stickers.isEmpty()) {
 | |
| 		sets.remove(Data::Stickers::DefaultSetId);
 | |
| 	} else {
 | |
| 		order.push_front(Data::Stickers::DefaultSetId);
 | |
| 	}
 | |
| 	if (custom->stickers.isEmpty()) {
 | |
| 		sets.remove(Data::Stickers::CustomSetId);
 | |
| 	}
 | |
| 
 | |
| 	writeInstalledStickers();
 | |
| 	writeSessionSettings();
 | |
| 
 | |
| 	ClearKey(_recentStickersKeyOld, _basePath);
 | |
| 	_recentStickersKeyOld = 0;
 | |
| 	writeMapDelayed();
 | |
| }
 | |
| 
 | |
| void Account::readInstalledStickers() {
 | |
| 	if (!_installedStickersKey) {
 | |
| 		return importOldRecentStickers();
 | |
| 	}
 | |
| 
 | |
| 	_owner->session().data().stickers().setsRef().clear();
 | |
| 	readStickerSets(
 | |
| 		_installedStickersKey,
 | |
| 		&_owner->session().data().stickers().setsOrderRef(),
 | |
| 		Data::StickersSetFlag::Installed);
 | |
| }
 | |
| 
 | |
| void Account::readFeaturedStickers() {
 | |
| 	readStickerSets(
 | |
| 		_featuredStickersKey,
 | |
| 		&_owner->session().data().stickers().featuredSetsOrderRef(),
 | |
| 		Data::StickersSetFlag::Featured);
 | |
| 
 | |
| 	const auto &sets = _owner->session().data().stickers().sets();
 | |
| 	const auto &order = _owner->session().data().stickers().featuredSetsOrder();
 | |
| 	int unreadCount = 0;
 | |
| 	for (const auto setId : order) {
 | |
| 		auto it = sets.find(setId);
 | |
| 		if (it != sets.cend()
 | |
| 			&& (it->second->flags & Data::StickersSetFlag::Unread)) {
 | |
| 			++unreadCount;
 | |
| 		}
 | |
| 	}
 | |
| 	_owner->session().data().stickers().setFeaturedSetsUnreadCount(unreadCount);
 | |
| }
 | |
| 
 | |
| void Account::readRecentStickers() {
 | |
| 	readStickerSets(_recentStickersKey);
 | |
| }
 | |
| 
 | |
| void Account::readRecentMasks() {
 | |
| 	readStickerSets(_recentMasksKey);
 | |
| }
 | |
| 
 | |
| void Account::readFavedStickers() {
 | |
| 	readStickerSets(_favedStickersKey);
 | |
| }
 | |
| 
 | |
| void Account::readArchivedStickers() {
 | |
| 	// TODO: refactor to support for multiple accounts.
 | |
| 	static bool archivedStickersRead = false;
 | |
| 	if (!archivedStickersRead) {
 | |
| 		readStickerSets(
 | |
| 			_archivedStickersKey,
 | |
| 			&_owner->session().data().stickers().archivedSetsOrderRef());
 | |
| 		archivedStickersRead = true;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void Account::readArchivedMasks() {
 | |
| 	// TODO: refactor to support for multiple accounts.
 | |
| 	static bool archivedMasksRead = false;
 | |
| 	if (!archivedMasksRead) {
 | |
| 		readStickerSets(
 | |
| 			_archivedMasksKey,
 | |
| 			&_owner->session().data().stickers().archivedMaskSetsOrderRef());
 | |
| 		archivedMasksRead = true;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void Account::readInstalledMasks() {
 | |
| 	readStickerSets(
 | |
| 		_installedMasksKey,
 | |
| 		&_owner->session().data().stickers().maskSetsOrderRef(),
 | |
| 		Data::StickersSetFlag::Installed);
 | |
| }
 | |
| 
 | |
| void Account::writeSavedGifs() {
 | |
| 	auto &saved = _owner->session().data().stickers().savedGifs();
 | |
| 	if (saved.isEmpty()) {
 | |
| 		if (_savedGifsKey) {
 | |
| 			ClearKey(_savedGifsKey, _basePath);
 | |
| 			_savedGifsKey = 0;
 | |
| 			writeMapDelayed();
 | |
| 		}
 | |
| 	} else {
 | |
| 		quint32 size = sizeof(quint32); // count
 | |
| 		for_const (auto gif, saved) {
 | |
| 			size += Serialize::Document::sizeInStream(gif);
 | |
| 		}
 | |
| 
 | |
| 		if (!_savedGifsKey) {
 | |
| 			_savedGifsKey = GenerateKey(_basePath);
 | |
| 			writeMapQueued();
 | |
| 		}
 | |
| 		EncryptedDescriptor data(size);
 | |
| 		data.stream << quint32(saved.size());
 | |
| 		for_const (auto gif, saved) {
 | |
| 			Serialize::Document::writeToStream(data.stream, gif);
 | |
| 		}
 | |
| 		FileWriteDescriptor file(_savedGifsKey, _basePath);
 | |
| 		file.writeEncrypted(data, _localKey);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void Account::readSavedGifs() {
 | |
| 	if (!_savedGifsKey) return;
 | |
| 
 | |
| 	FileReadDescriptor gifs;
 | |
| 	if (!ReadEncryptedFile(gifs, _savedGifsKey, _basePath, _localKey)) {
 | |
| 		ClearKey(_savedGifsKey, _basePath);
 | |
| 		_savedGifsKey = 0;
 | |
| 		writeMapDelayed();
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	auto &saved = _owner->session().data().stickers().savedGifsRef();
 | |
| 	const auto failed = [&] {
 | |
| 		ClearKey(_savedGifsKey, _basePath);
 | |
| 		_savedGifsKey = 0;
 | |
| 		saved.clear();
 | |
| 	};
 | |
| 	saved.clear();
 | |
| 
 | |
| 	quint32 cnt;
 | |
| 	gifs.stream >> cnt;
 | |
| 	saved.reserve(cnt);
 | |
| 	OrderedSet<DocumentId> read;
 | |
| 	for (uint32 i = 0; i < cnt; ++i) {
 | |
| 		auto document = Serialize::Document::readFromStream(
 | |
| 			&_owner->session(),
 | |
| 			gifs.version,
 | |
| 			gifs.stream);
 | |
| 		if (!CheckStreamStatus(gifs.stream)) {
 | |
| 			return failed();
 | |
| 		} else if (!document || !document->isGifv()) {
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		if (read.contains(document->id)) continue;
 | |
| 		read.insert(document->id);
 | |
| 
 | |
| 		saved.push_back(document);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void Account::writeRecentHashtagsAndBots() {
 | |
| 	const auto &write = cRecentWriteHashtags();
 | |
| 	const auto &search = cRecentSearchHashtags();
 | |
| 	const auto &bots = cRecentInlineBots();
 | |
| 
 | |
| 	if (write.isEmpty() && search.isEmpty() && bots.isEmpty()) {
 | |
| 		readRecentHashtagsAndBots();
 | |
| 	}
 | |
| 	if (write.isEmpty() && search.isEmpty() && bots.isEmpty()) {
 | |
| 		if (_recentHashtagsAndBotsKey) {
 | |
| 			ClearKey(_recentHashtagsAndBotsKey, _basePath);
 | |
| 			_recentHashtagsAndBotsKey = 0;
 | |
| 			writeMapDelayed();
 | |
| 		}
 | |
| 		return;
 | |
| 	}
 | |
| 	if (!_recentHashtagsAndBotsKey) {
 | |
| 		_recentHashtagsAndBotsKey = GenerateKey(_basePath);
 | |
| 		writeMapQueued();
 | |
| 	}
 | |
| 	quint32 size = sizeof(quint32) * 3, writeCnt = 0, searchCnt = 0, botsCnt = cRecentInlineBots().size();
 | |
| 	for (auto i = write.cbegin(), e = write.cend(); i != e;  ++i) {
 | |
| 		if (!i->first.isEmpty()) {
 | |
| 			size += Serialize::stringSize(i->first) + sizeof(quint16);
 | |
| 			++writeCnt;
 | |
| 		}
 | |
| 	}
 | |
| 	for (auto i = search.cbegin(), e = search.cend(); i != e; ++i) {
 | |
| 		if (!i->first.isEmpty()) {
 | |
| 			size += Serialize::stringSize(i->first) + sizeof(quint16);
 | |
| 			++searchCnt;
 | |
| 		}
 | |
| 	}
 | |
| 	for (auto i = bots.cbegin(), e = bots.cend(); i != e; ++i) {
 | |
| 		size += Serialize::peerSize(*i);
 | |
| 	}
 | |
| 
 | |
| 	EncryptedDescriptor data(size);
 | |
| 	data.stream << quint32(writeCnt) << quint32(searchCnt);
 | |
| 	for (auto i = write.cbegin(), e = write.cend(); i != e; ++i) {
 | |
| 		if (!i->first.isEmpty()) data.stream << i->first << quint16(i->second);
 | |
| 	}
 | |
| 	for (auto i = search.cbegin(), e = search.cend(); i != e; ++i) {
 | |
| 		if (!i->first.isEmpty()) data.stream << i->first << quint16(i->second);
 | |
| 	}
 | |
| 	data.stream << quint32(botsCnt);
 | |
| 	for (auto i = bots.cbegin(), e = bots.cend(); i != e; ++i) {
 | |
| 		Serialize::writePeer(data.stream, *i);
 | |
| 	}
 | |
| 	FileWriteDescriptor file(_recentHashtagsAndBotsKey, _basePath);
 | |
| 	file.writeEncrypted(data, _localKey);
 | |
| }
 | |
| 
 | |
| void Account::readRecentHashtagsAndBots() {
 | |
| 	if (_recentHashtagsAndBotsWereRead) return;
 | |
| 	_recentHashtagsAndBotsWereRead = true;
 | |
| 
 | |
| 	if (!_recentHashtagsAndBotsKey) return;
 | |
| 
 | |
| 	FileReadDescriptor hashtags;
 | |
| 	if (!ReadEncryptedFile(hashtags, _recentHashtagsAndBotsKey, _basePath, _localKey)) {
 | |
| 		ClearKey(_recentHashtagsAndBotsKey, _basePath);
 | |
| 		_recentHashtagsAndBotsKey = 0;
 | |
| 		writeMapDelayed();
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	quint32 writeCount = 0, searchCount = 0, botsCount = 0;
 | |
| 	hashtags.stream >> writeCount >> searchCount;
 | |
| 
 | |
| 	QString tag;
 | |
| 	quint16 count;
 | |
| 
 | |
| 	RecentHashtagPack write, search;
 | |
| 	RecentInlineBots bots;
 | |
| 	if (writeCount) {
 | |
| 		write.reserve(writeCount);
 | |
| 		for (uint32 i = 0; i < writeCount; ++i) {
 | |
| 			hashtags.stream >> tag >> count;
 | |
| 			write.push_back(qMakePair(tag.trimmed(), count));
 | |
| 		}
 | |
| 	}
 | |
| 	if (searchCount) {
 | |
| 		search.reserve(searchCount);
 | |
| 		for (uint32 i = 0; i < searchCount; ++i) {
 | |
| 			hashtags.stream >> tag >> count;
 | |
| 			search.push_back(qMakePair(tag.trimmed(), count));
 | |
| 		}
 | |
| 	}
 | |
| 	cSetRecentWriteHashtags(write);
 | |
| 	cSetRecentSearchHashtags(search);
 | |
| 
 | |
| 	if (!hashtags.stream.atEnd()) {
 | |
| 		hashtags.stream >> botsCount;
 | |
| 		if (botsCount) {
 | |
| 			bots.reserve(botsCount);
 | |
| 			for (auto i = 0; i < botsCount; ++i) {
 | |
| 				const auto peer = Serialize::readPeer(
 | |
| 					&_owner->session(),
 | |
| 					hashtags.version,
 | |
| 					hashtags.stream);
 | |
| 				if (!peer) {
 | |
| 					return; // Broken data.
 | |
| 				} else if (peer->isUser()
 | |
| 					&& peer->asUser()->isBot()
 | |
| 					&& !peer->asUser()->botInfo->inlinePlaceholder.isEmpty()
 | |
| 					&& !peer->asUser()->username.isEmpty()) {
 | |
| 					bots.push_back(peer->asUser());
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		cSetRecentInlineBots(bots);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| std::optional<RecentHashtagPack> Account::saveRecentHashtags(
 | |
| 		Fn<RecentHashtagPack()> getPack,
 | |
| 		const QString &text) {
 | |
| 	auto found = false;
 | |
| 	auto m = QRegularExpressionMatch();
 | |
| 	auto recent = getPack();
 | |
| 	for (auto i = 0, next = 0; (m = TextUtilities::RegExpHashtag().match(text, i)).hasMatch(); i = next) {
 | |
| 		i = m.capturedStart();
 | |
| 		next = m.capturedEnd();
 | |
| 		if (m.hasMatch()) {
 | |
| 			if (!m.capturedRef(1).isEmpty()) {
 | |
| 				++i;
 | |
| 			}
 | |
| 			if (!m.capturedRef(2).isEmpty()) {
 | |
| 				--next;
 | |
| 			}
 | |
| 		}
 | |
| 		const auto tag = text.mid(i + 1, next - i - 1);
 | |
| 		if (TextUtilities::RegExpHashtagExclude().match(tag).hasMatch()) {
 | |
| 			continue;
 | |
| 		}
 | |
| 		if (!found
 | |
| 			&& cRecentWriteHashtags().isEmpty()
 | |
| 			&& cRecentSearchHashtags().isEmpty()) {
 | |
| 			readRecentHashtagsAndBots();
 | |
| 			recent = getPack();
 | |
| 		}
 | |
| 		found = true;
 | |
| 		Local::incrementRecentHashtag(recent, tag);
 | |
| 	}
 | |
| 	return found ? base::make_optional(recent) : std::nullopt;
 | |
| }
 | |
| 
 | |
| void Account::saveRecentSentHashtags(const QString &text) {
 | |
| 	const auto result = saveRecentHashtags(
 | |
| 		[] { return cRecentWriteHashtags(); },
 | |
| 		text);
 | |
| 	if (result) {
 | |
| 		cSetRecentWriteHashtags(*result);
 | |
| 		writeRecentHashtagsAndBots();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void Account::saveRecentSearchHashtags(const QString &text) {
 | |
| 	const auto result = saveRecentHashtags(
 | |
| 		[] { return cRecentSearchHashtags(); },
 | |
| 		text);
 | |
| 	if (result) {
 | |
| 		cSetRecentSearchHashtags(*result);
 | |
| 		writeRecentHashtagsAndBots();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void Account::writeExportSettings(const Export::Settings &settings) {
 | |
| 	const auto check = Export::Settings();
 | |
| 	if (settings.types == check.types
 | |
| 		&& settings.fullChats == check.fullChats
 | |
| 		&& settings.media.types == check.media.types
 | |
| 		&& settings.media.sizeLimit == check.media.sizeLimit
 | |
| 		&& settings.path == check.path
 | |
| 		&& settings.format == check.format
 | |
| 		&& settings.availableAt == check.availableAt
 | |
| 		&& !settings.onlySinglePeer()) {
 | |
| 		if (_exportSettingsKey) {
 | |
| 			ClearKey(_exportSettingsKey, _basePath);
 | |
| 			_exportSettingsKey = 0;
 | |
| 			writeMapDelayed();
 | |
| 		}
 | |
| 		return;
 | |
| 	}
 | |
| 	if (!_exportSettingsKey) {
 | |
| 		_exportSettingsKey = GenerateKey(_basePath);
 | |
| 		writeMapQueued();
 | |
| 	}
 | |
| 	quint32 size = sizeof(quint32) * 6
 | |
| 		+ Serialize::stringSize(settings.path)
 | |
| 		+ sizeof(qint32) * 2 + sizeof(quint64);
 | |
| 	EncryptedDescriptor data(size);
 | |
| 	data.stream
 | |
| 		<< quint32(settings.types)
 | |
| 		<< quint32(settings.fullChats)
 | |
| 		<< quint32(settings.media.types)
 | |
| 		<< quint32(settings.media.sizeLimit)
 | |
| 		<< quint32(settings.format)
 | |
| 		<< settings.path
 | |
| 		<< quint32(settings.availableAt);
 | |
| 	settings.singlePeer.match([&](const MTPDinputPeerUser & user) {
 | |
| 		data.stream
 | |
| 			<< kSinglePeerTypeUser
 | |
| 			<< qint32(user.vuser_id().v)
 | |
| 			<< quint64(user.vaccess_hash().v);
 | |
| 	}, [&](const MTPDinputPeerChat & chat) {
 | |
| 		data.stream << kSinglePeerTypeChat << qint32(chat.vchat_id().v);
 | |
| 	}, [&](const MTPDinputPeerChannel & channel) {
 | |
| 		data.stream
 | |
| 			<< kSinglePeerTypeChannel
 | |
| 			<< qint32(channel.vchannel_id().v)
 | |
| 			<< quint64(channel.vaccess_hash().v);
 | |
| 	}, [&](const MTPDinputPeerSelf &) {
 | |
| 		data.stream << kSinglePeerTypeSelf;
 | |
| 	}, [&](const MTPDinputPeerEmpty &) {
 | |
| 		data.stream << kSinglePeerTypeEmpty;
 | |
| 	}, [&](const MTPDinputPeerUserFromMessage &) {
 | |
| 		Unexpected("From message peer in single peer export settings.");
 | |
| 	}, [&](const MTPDinputPeerChannelFromMessage &) {
 | |
| 		Unexpected("From message peer in single peer export settings.");
 | |
| 	});
 | |
| 	data.stream << qint32(settings.singlePeerFrom);
 | |
| 	data.stream << qint32(settings.singlePeerTill);
 | |
| 
 | |
| 	FileWriteDescriptor file(_exportSettingsKey, _basePath);
 | |
| 	file.writeEncrypted(data, _localKey);
 | |
| }
 | |
| 
 | |
| Export::Settings Account::readExportSettings() {
 | |
| 	FileReadDescriptor file;
 | |
| 	if (!ReadEncryptedFile(file, _exportSettingsKey, _basePath, _localKey)) {
 | |
| 		ClearKey(_exportSettingsKey, _basePath);
 | |
| 		_exportSettingsKey = 0;
 | |
| 		writeMapDelayed();
 | |
| 		return Export::Settings();
 | |
| 	}
 | |
| 
 | |
| 	quint32 types = 0, fullChats = 0;
 | |
| 	quint32 mediaTypes = 0, mediaSizeLimit = 0;
 | |
| 	quint32 format = 0, availableAt = 0;
 | |
| 	QString path;
 | |
| 	qint32 singlePeerType = 0, singlePeerBareId = 0;
 | |
| 	quint64 singlePeerAccessHash = 0;
 | |
| 	qint32 singlePeerFrom = 0, singlePeerTill = 0;
 | |
| 	file.stream
 | |
| 		>> types
 | |
| 		>> fullChats
 | |
| 		>> mediaTypes
 | |
| 		>> mediaSizeLimit
 | |
| 		>> format
 | |
| 		>> path
 | |
| 		>> availableAt;
 | |
| 	if (!file.stream.atEnd()) {
 | |
| 		file.stream >> singlePeerType;
 | |
| 		switch (singlePeerType) {
 | |
| 		case kSinglePeerTypeUser:
 | |
| 		case kSinglePeerTypeChannel: {
 | |
| 			file.stream >> singlePeerBareId >> singlePeerAccessHash;
 | |
| 		} break;
 | |
| 		case kSinglePeerTypeChat: file.stream >> singlePeerBareId; break;
 | |
| 		case kSinglePeerTypeSelf:
 | |
| 		case kSinglePeerTypeEmpty: break;
 | |
| 		default: return Export::Settings();
 | |
| 		}
 | |
| 	}
 | |
| 	if (!file.stream.atEnd()) {
 | |
| 		file.stream >> singlePeerFrom >> singlePeerTill;
 | |
| 	}
 | |
| 	auto result = Export::Settings();
 | |
| 	result.types = Export::Settings::Types::from_raw(types);
 | |
| 	result.fullChats = Export::Settings::Types::from_raw(fullChats);
 | |
| 	result.media.types = Export::MediaSettings::Types::from_raw(mediaTypes);
 | |
| 	result.media.sizeLimit = mediaSizeLimit;
 | |
| 	result.format = Export::Output::Format(format);
 | |
| 	result.path = path;
 | |
| 	result.availableAt = availableAt;
 | |
| 	result.singlePeer = [&] {
 | |
| 		switch (singlePeerType) {
 | |
| 		case kSinglePeerTypeUser:
 | |
| 			return MTP_inputPeerUser(
 | |
| 				MTP_int(singlePeerBareId),
 | |
| 				MTP_long(singlePeerAccessHash));
 | |
| 		case kSinglePeerTypeChat:
 | |
| 			return MTP_inputPeerChat(MTP_int(singlePeerBareId));
 | |
| 		case kSinglePeerTypeChannel:
 | |
| 			return MTP_inputPeerChannel(
 | |
| 				MTP_int(singlePeerBareId),
 | |
| 				MTP_long(singlePeerAccessHash));
 | |
| 		case kSinglePeerTypeSelf:
 | |
| 			return MTP_inputPeerSelf();
 | |
| 		case kSinglePeerTypeEmpty:
 | |
| 			return MTP_inputPeerEmpty();
 | |
| 		}
 | |
| 		Unexpected("Type in export data single peer.");
 | |
| 	}();
 | |
| 	result.singlePeerFrom = singlePeerFrom;
 | |
| 	result.singlePeerTill = singlePeerTill;
 | |
| 	return (file.stream.status() == QDataStream::Ok && result.validate())
 | |
| 		? result
 | |
| 		: Export::Settings();
 | |
| }
 | |
| 
 | |
| void Account::writeSelf() {
 | |
| 	writeMapDelayed();
 | |
| }
 | |
| 
 | |
| void Account::readSelf(
 | |
| 		not_null<Main::Session*> session,
 | |
| 		const QByteArray &serialized,
 | |
| 		int32 streamVersion) {
 | |
| 	QDataStream stream(serialized);
 | |
| 	const auto user = session->user();
 | |
| 	const auto wasLoadedStatus = user->loadedStatus();
 | |
| 	user->setLoadedStatus(PeerData::LoadedStatus::Not);
 | |
| 	const auto self = Serialize::readPeer(
 | |
| 		session,
 | |
| 		streamVersion,
 | |
| 		stream);
 | |
| 	if (!self || !self->isSelf() || self != user) {
 | |
| 		user->setLoadedStatus(wasLoadedStatus);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	QString about;
 | |
| 	stream >> about;
 | |
| 	if (CheckStreamStatus(stream)) {
 | |
| 		self->asUser()->setAbout(about);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void Account::writeTrustedBots() {
 | |
| 	if (_trustedBots.empty()) {
 | |
| 		if (_trustedBotsKey) {
 | |
| 			ClearKey(_trustedBotsKey, _basePath);
 | |
| 			_trustedBotsKey = 0;
 | |
| 			writeMapDelayed();
 | |
| 		}
 | |
| 		return;
 | |
| 	}
 | |
| 	if (!_trustedBotsKey) {
 | |
| 		_trustedBotsKey = GenerateKey(_basePath);
 | |
| 		writeMapQueued();
 | |
| 	}
 | |
| 	quint32 size = sizeof(qint32) + _trustedBots.size() * sizeof(quint64);
 | |
| 	EncryptedDescriptor data(size);
 | |
| 	data.stream << qint32(_trustedBots.size());
 | |
| 	for (const auto &[peerId, mask] : _trustedBots) {
 | |
| 		// value: 8 bit mask, 56 bit bot peer_id.
 | |
| 		auto value = SerializePeerId(peerId);
 | |
| 		Assert((value >> 56) == 0);
 | |
| 		value |= (quint64(mask) << 56);
 | |
| 		data.stream << value;
 | |
| 	}
 | |
| 
 | |
| 	FileWriteDescriptor file(_trustedBotsKey, _basePath);
 | |
| 	file.writeEncrypted(data, _localKey);
 | |
| }
 | |
| 
 | |
| void Account::readTrustedBots() {
 | |
| 	if (!_trustedBotsKey) return;
 | |
| 
 | |
| 	FileReadDescriptor trusted;
 | |
| 	if (!ReadEncryptedFile(trusted, _trustedBotsKey, _basePath, _localKey)) {
 | |
| 		ClearKey(_trustedBotsKey, _basePath);
 | |
| 		_trustedBotsKey = 0;
 | |
| 		writeMapDelayed();
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	qint32 size = 0;
 | |
| 	trusted.stream >> size;
 | |
| 	for (int i = 0; i < size; ++i) {
 | |
| 		auto value = quint64();
 | |
| 		trusted.stream >> value;
 | |
| 		const auto mask = base::flags<BotTrustFlag>::from_raw(
 | |
| 			uchar(value >> 56));
 | |
| 		const auto peerIdSerialized = value & ~(0xFFULL << 56);
 | |
| 		const auto peerId = DeserializePeerId(peerIdSerialized);
 | |
| 		_trustedBots.emplace(peerId, mask);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void Account::markBotTrustedOpenGame(PeerId botId) {
 | |
| 	if (isBotTrustedOpenGame(botId)) {
 | |
| 		return;
 | |
| 	}
 | |
| 	const auto i = _trustedBots.find(botId);
 | |
| 	if (i == end(_trustedBots)) {
 | |
| 		_trustedBots.emplace(botId, BotTrustFlag());
 | |
| 	} else {
 | |
| 		i->second &= ~BotTrustFlag::NoOpenGame;
 | |
| 	}
 | |
| 	writeTrustedBots();
 | |
| }
 | |
| 
 | |
| bool Account::isBotTrustedOpenGame(PeerId botId) {
 | |
| 	if (!_trustedBotsRead) {
 | |
| 		readTrustedBots();
 | |
| 		_trustedBotsRead = true;
 | |
| 	}
 | |
| 	const auto i = _trustedBots.find(botId);
 | |
| 	return (i != end(_trustedBots))
 | |
| 		&& ((i->second & BotTrustFlag::NoOpenGame) == 0);
 | |
| }
 | |
| 
 | |
| void Account::markBotTrustedPayment(PeerId botId) {
 | |
| 	if (isBotTrustedPayment(botId)) {
 | |
| 		return;
 | |
| 	}
 | |
| 	const auto i = _trustedBots.find(botId);
 | |
| 	if (i == end(_trustedBots)) {
 | |
| 		_trustedBots.emplace(
 | |
| 			botId,
 | |
| 			BotTrustFlag::NoOpenGame | BotTrustFlag::Payment);
 | |
| 	} else {
 | |
| 		i->second |= BotTrustFlag::Payment;
 | |
| 	}
 | |
| 	writeTrustedBots();
 | |
| }
 | |
| 
 | |
| bool Account::isBotTrustedPayment(PeerId botId) {
 | |
| 	if (!_trustedBotsRead) {
 | |
| 		readTrustedBots();
 | |
| 		_trustedBotsRead = true;
 | |
| 	}
 | |
| 	const auto i = _trustedBots.find(botId);
 | |
| 	return (i != end(_trustedBots))
 | |
| 		&& ((i->second & BotTrustFlag::Payment) != 0);
 | |
| }
 | |
| 
 | |
| bool Account::encrypt(
 | |
| 		const void *src,
 | |
| 		void *dst,
 | |
| 		uint32 len,
 | |
| 		const void *key128) const {
 | |
| 	if (!_localKey) {
 | |
| 		return false;
 | |
| 	}
 | |
| 	MTP::aesEncryptLocal(src, dst, len, _localKey, key128);
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| bool Account::decrypt(
 | |
| 		const void *src,
 | |
| 		void *dst,
 | |
| 		uint32 len,
 | |
| 		const void *key128) const {
 | |
| 	if (!_localKey) {
 | |
| 		return false;
 | |
| 	}
 | |
| 	MTP::aesDecryptLocal(src, dst, len, _localKey, key128);
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| } // namespace Storage
 | 
