5146 lines
		
	
	
	
		
			141 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			5146 lines
		
	
	
	
		
			141 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/localstorage.h"
 | |
| 
 | |
| #include "storage/serialize_document.h"
 | |
| #include "storage/serialize_common.h"
 | |
| #include "storage/storage_encrypted_file.h"
 | |
| #include "storage/storage_clear_legacy.h"
 | |
| #include "chat_helpers/stickers.h"
 | |
| #include "data/data_drafts.h"
 | |
| #include "data/data_user.h"
 | |
| #include "boxes/send_files_box.h"
 | |
| #include "ui/widgets/input_fields.h"
 | |
| #include "ui/emoji_config.h"
 | |
| #include "export/export_settings.h"
 | |
| #include "api/api_hash.h"
 | |
| #include "core/crash_reports.h"
 | |
| #include "core/update_checker.h"
 | |
| #include "observer_peer.h"
 | |
| #include "mainwidget.h"
 | |
| #include "mainwindow.h"
 | |
| #include "lang/lang_keys.h"
 | |
| #include "lang/lang_cloud_manager.h"
 | |
| #include "main/main_account.h"
 | |
| #include "media/audio/media_audio.h"
 | |
| #include "mtproto/dc_options.h"
 | |
| #include "core/application.h"
 | |
| #include "apiwrap.h"
 | |
| #include "main/main_session.h"
 | |
| #include "window/themes/window_theme.h"
 | |
| #include "window/window_session_controller.h"
 | |
| #include "base/flags.h"
 | |
| #include "data/data_session.h"
 | |
| #include "history/history.h"
 | |
| #include "facades.h"
 | |
| 
 | |
| #include <QtCore/QBuffer>
 | |
| #include <QtCore/QtEndian>
 | |
| #include <QtCore/QDirIterator>
 | |
| 
 | |
| extern "C" {
 | |
| #include <openssl/evp.h>
 | |
| } // extern "C"
 | |
| 
 | |
| namespace Local {
 | |
| namespace {
 | |
| 
 | |
| constexpr auto kThemeFileSizeLimit = 5 * 1024 * 1024;
 | |
| constexpr auto kFileLoaderQueueStopTimeout = crl::time(5000);
 | |
| constexpr auto kDefaultStickerInstallDate = TimeId(1);
 | |
| constexpr auto kProxyTypeShift = 1024;
 | |
| constexpr auto kWriteMapTimeout = crl::time(1000);
 | |
| constexpr auto kSavedBackgroundFormat = QImage::Format_ARGB32_Premultiplied;
 | |
| 
 | |
| constexpr auto kWallPaperLegacySerializeTagId = int32(-111);
 | |
| constexpr auto kWallPaperSerializeTagId = int32(-112);
 | |
| constexpr auto kWallPaperSidesLimit = 10'000;
 | |
| 
 | |
| 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 kStickersVersionTag = quint32(-1);
 | |
| constexpr auto kStickersSerializeVersion = 1;
 | |
| constexpr auto kMaxSavedStickerSetsCount = 1000;
 | |
| 
 | |
| const auto kThemeNewPathRelativeTag = qstr("special://new_tag");
 | |
| 
 | |
| using Database = Storage::Cache::Database;
 | |
| using FileKey = quint64;
 | |
| 
 | |
| constexpr char tdfMagic[] = { 'T', 'D', 'F', '$' };
 | |
| constexpr auto tdfMagicLen = int(sizeof(tdfMagic));
 | |
| 
 | |
| QString toFilePart(FileKey val) {
 | |
| 	QString result;
 | |
| 	result.reserve(0x10);
 | |
| 	for (int32 i = 0; i < 0x10; ++i) {
 | |
| 		uchar v = (val & 0x0F);
 | |
| 		result.push_back((v < 0x0A) ? ('0' + v) : ('A' + (v - 0x0A)));
 | |
| 		val >>= 4;
 | |
| 	}
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| QString _basePath, _userBasePath, _userDbPath;
 | |
| 
 | |
| bool _started = false;
 | |
| internal::Manager *_manager = nullptr;
 | |
| TaskQueue *_localLoader = nullptr;
 | |
| 
 | |
| bool _working() {
 | |
| 	return _manager && !_basePath.isEmpty();
 | |
| }
 | |
| 
 | |
| bool _userWorking() {
 | |
| 	return _manager && !_basePath.isEmpty() && !_userBasePath.isEmpty();
 | |
| }
 | |
| 
 | |
| enum class FileOption {
 | |
| 	User = (1 << 0),
 | |
| 	Safe = (1 << 1),
 | |
| };
 | |
| using FileOptions = base::flags<FileOption>;
 | |
| inline constexpr auto is_flag_type(FileOption) { return true; };
 | |
| 
 | |
| bool keyAlreadyUsed(QString &name, FileOptions options = FileOption::User | FileOption::Safe) {
 | |
| 	name += '0';
 | |
| 	if (QFileInfo(name).exists()) return true;
 | |
| 	if (options & (FileOption::Safe)) {
 | |
| 		name[name.size() - 1] = '1';
 | |
| 		return QFileInfo(name).exists();
 | |
| 	}
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| FileKey genKey(FileOptions options = FileOption::User | FileOption::Safe) {
 | |
| 	if (options & FileOption::User) {
 | |
| 		if (!_userWorking()) return 0;
 | |
| 	} else {
 | |
| 		if (!_working()) return 0;
 | |
| 	}
 | |
| 
 | |
| 	FileKey result;
 | |
| 	QString base = (options & FileOption::User) ? _userBasePath : _basePath, path;
 | |
| 	path.reserve(base.size() + 0x11);
 | |
| 	path += base;
 | |
| 	do {
 | |
| 		result = rand_value<FileKey>();
 | |
| 		path.resize(base.size());
 | |
| 		path += toFilePart(result);
 | |
| 	} while (!result || keyAlreadyUsed(path, options));
 | |
| 
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| void clearKey(const FileKey &key, FileOptions options = FileOption::User | FileOption::Safe) {
 | |
| 	if (options & FileOption::User) {
 | |
| 		if (!_userWorking()) return;
 | |
| 	} else {
 | |
| 		if (!_working()) return;
 | |
| 	}
 | |
| 
 | |
| 	QString base = (options & FileOption::User) ? _userBasePath : _basePath, name;
 | |
| 	name.reserve(base.size() + 0x11);
 | |
| 	name.append(base).append(toFilePart(key)).append('0');
 | |
| 	QFile::remove(name);
 | |
| 	if (options & FileOption::Safe) {
 | |
| 		name[name.size() - 1] = '1';
 | |
| 		QFile::remove(name);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool _checkStreamStatus(QDataStream &stream) {
 | |
| 	if (stream.status() != QDataStream::Ok) {
 | |
| 		LOG(("Bad data stream status: %1").arg(stream.status()));
 | |
| 		return false;
 | |
| 	}
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| QByteArray _settingsSalt, _passKeySalt, _passKeyEncrypted;
 | |
| 
 | |
| constexpr auto kLocalKeySize = MTP::AuthKey::kSize;
 | |
| 
 | |
| auto OldKey = MTP::AuthKeyPtr();
 | |
| auto SettingsKey = MTP::AuthKeyPtr();
 | |
| auto PassKey = MTP::AuthKeyPtr();
 | |
| auto LocalKey = MTP::AuthKeyPtr();
 | |
| 
 | |
| void createLocalKey(const QByteArray &pass, QByteArray *salt, MTP::AuthKeyPtr *result) {
 | |
| 	auto key = MTP::AuthKey::Data { { gsl::byte{} } };
 | |
| 	auto iterCount = pass.size() ? LocalEncryptIterCount : LocalEncryptNoPwdIterCount; // dont slow down for no password
 | |
| 	auto newSalt = QByteArray();
 | |
| 	if (!salt) {
 | |
| 		newSalt.resize(LocalEncryptSaltSize);
 | |
| 		memset_rand(newSalt.data(), newSalt.size());
 | |
| 		salt = &newSalt;
 | |
| 
 | |
| 		cSetLocalSalt(newSalt);
 | |
| 	}
 | |
| 
 | |
| 	PKCS5_PBKDF2_HMAC_SHA1(pass.constData(), pass.size(), (uchar*)salt->data(), salt->size(), iterCount, key.size(), (uchar*)key.data());
 | |
| 
 | |
| 	*result = std::make_shared<MTP::AuthKey>(key);
 | |
| }
 | |
| 
 | |
| struct FileReadDescriptor {
 | |
| 	int32 version = 0;
 | |
| 	QByteArray data;
 | |
| 	QBuffer buffer;
 | |
| 	QDataStream stream;
 | |
| 	~FileReadDescriptor() {
 | |
| 		if (version) {
 | |
| 			stream.setDevice(nullptr);
 | |
| 			if (buffer.isOpen()) buffer.close();
 | |
| 			buffer.setBuffer(nullptr);
 | |
| 		}
 | |
| 	}
 | |
| };
 | |
| 
 | |
| struct EncryptedDescriptor {
 | |
| 	EncryptedDescriptor() {
 | |
| 	}
 | |
| 	EncryptedDescriptor(uint32 size) {
 | |
| 		uint32 fullSize = sizeof(uint32) + size;
 | |
| 		if (fullSize & 0x0F) fullSize += 0x10 - (fullSize & 0x0F);
 | |
| 		data.reserve(fullSize);
 | |
| 
 | |
| 		data.resize(sizeof(uint32));
 | |
| 		buffer.setBuffer(&data);
 | |
| 		buffer.open(QIODevice::WriteOnly);
 | |
| 		buffer.seek(sizeof(uint32));
 | |
| 		stream.setDevice(&buffer);
 | |
| 		stream.setVersion(QDataStream::Qt_5_1);
 | |
| 	}
 | |
| 	QByteArray data;
 | |
| 	QBuffer buffer;
 | |
| 	QDataStream stream;
 | |
| 	void finish() {
 | |
| 		if (stream.device()) stream.setDevice(nullptr);
 | |
| 		if (buffer.isOpen()) buffer.close();
 | |
| 		buffer.setBuffer(nullptr);
 | |
| 	}
 | |
| 	~EncryptedDescriptor() {
 | |
| 		finish();
 | |
| 	}
 | |
| };
 | |
| 
 | |
| struct FileWriteDescriptor {
 | |
| 	FileWriteDescriptor(const FileKey &key, FileOptions options = FileOption::User | FileOption::Safe) {
 | |
| 		init(toFilePart(key), options);
 | |
| 	}
 | |
| 	FileWriteDescriptor(const QString &name, FileOptions options = FileOption::User | FileOption::Safe) {
 | |
| 		init(name, options);
 | |
| 	}
 | |
| 	void init(const QString &name, FileOptions options) {
 | |
| 		if (options & FileOption::User) {
 | |
| 			if (!_userWorking()) return;
 | |
| 		} else {
 | |
| 			if (!_working()) return;
 | |
| 		}
 | |
| 
 | |
| 		// detect order of read attempts and file version
 | |
| 		QString toTry[2];
 | |
| 		toTry[0] = ((options & FileOption::User) ? _userBasePath : _basePath) + name + '0';
 | |
| 		if (options & FileOption::Safe) {
 | |
| 			toTry[1] = ((options & FileOption::User) ? _userBasePath : _basePath) + name + '1';
 | |
| 			QFileInfo toTry0(toTry[0]);
 | |
| 			QFileInfo toTry1(toTry[1]);
 | |
| 			if (toTry0.exists()) {
 | |
| 				if (toTry1.exists()) {
 | |
| 					QDateTime mod0 = toTry0.lastModified(), mod1 = toTry1.lastModified();
 | |
| 					if (mod0 > mod1) {
 | |
| 						qSwap(toTry[0], toTry[1]);
 | |
| 					}
 | |
| 				} else {
 | |
| 					qSwap(toTry[0], toTry[1]);
 | |
| 				}
 | |
| 				toDelete = toTry[1];
 | |
| 			} else if (toTry1.exists()) {
 | |
| 				toDelete = toTry[1];
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		file.setFileName(toTry[0]);
 | |
| 		if (file.open(QIODevice::WriteOnly)) {
 | |
| 			file.write(tdfMagic, tdfMagicLen);
 | |
| 			qint32 version = AppVersion;
 | |
| 			file.write((const char*)&version, sizeof(version));
 | |
| 
 | |
| 			stream.setDevice(&file);
 | |
| 			stream.setVersion(QDataStream::Qt_5_1);
 | |
| 		}
 | |
| 	}
 | |
| 	bool writeData(const QByteArray &data) {
 | |
| 		if (!file.isOpen()) return false;
 | |
| 
 | |
| 		stream << data;
 | |
| 		quint32 len = data.isNull() ? 0xffffffff : data.size();
 | |
| 		if (QSysInfo::ByteOrder != QSysInfo::BigEndian) {
 | |
| 			len = qbswap(len);
 | |
| 		}
 | |
| 		md5.feed(&len, sizeof(len));
 | |
| 		md5.feed(data.constData(), data.size());
 | |
| 		dataSize += sizeof(len) + data.size();
 | |
| 
 | |
| 		return true;
 | |
| 	}
 | |
| 	static QByteArray prepareEncrypted(EncryptedDescriptor &data, const MTP::AuthKeyPtr &key = LocalKey) {
 | |
| 		data.finish();
 | |
| 		QByteArray &toEncrypt(data.data);
 | |
| 
 | |
| 		// prepare for encryption
 | |
| 		uint32 size = toEncrypt.size(), fullSize = size;
 | |
| 		if (fullSize & 0x0F) {
 | |
| 			fullSize += 0x10 - (fullSize & 0x0F);
 | |
| 			toEncrypt.resize(fullSize);
 | |
| 			memset_rand(toEncrypt.data() + size, fullSize - size);
 | |
| 		}
 | |
| 		*(uint32*)toEncrypt.data() = size;
 | |
| 		QByteArray encrypted(0x10 + fullSize, Qt::Uninitialized); // 128bit of sha1 - key128, sizeof(data), data
 | |
| 		hashSha1(toEncrypt.constData(), toEncrypt.size(), encrypted.data());
 | |
| 		MTP::aesEncryptLocal(toEncrypt.constData(), encrypted.data() + 0x10, fullSize, key, encrypted.constData());
 | |
| 
 | |
| 		return encrypted;
 | |
| 	}
 | |
| 	bool writeEncrypted(EncryptedDescriptor &data, const MTP::AuthKeyPtr &key = LocalKey) {
 | |
| 		return writeData(prepareEncrypted(data, key));
 | |
| 	}
 | |
| 	void finish() {
 | |
| 		if (!file.isOpen()) return;
 | |
| 
 | |
| 		stream.setDevice(nullptr);
 | |
| 
 | |
| 		md5.feed(&dataSize, sizeof(dataSize));
 | |
| 		qint32 version = AppVersion;
 | |
| 		md5.feed(&version, sizeof(version));
 | |
| 		md5.feed(tdfMagic, tdfMagicLen);
 | |
| 		file.write((const char*)md5.result(), 0x10);
 | |
| 		file.close();
 | |
| 
 | |
| 		if (!toDelete.isEmpty()) {
 | |
| 			QFile::remove(toDelete);
 | |
| 		}
 | |
| 	}
 | |
| 	QFile file;
 | |
| 	QDataStream stream;
 | |
| 
 | |
| 	QString toDelete;
 | |
| 
 | |
| 	HashMd5 md5;
 | |
| 	int32 dataSize = 0;
 | |
| 
 | |
| 	~FileWriteDescriptor() {
 | |
| 		finish();
 | |
| 	}
 | |
| };
 | |
| 
 | |
| bool readFile(FileReadDescriptor &result, const QString &name, FileOptions options = FileOption::User | FileOption::Safe) {
 | |
| 	if (options & FileOption::User) {
 | |
| 		if (!_userWorking()) return false;
 | |
| 	} else {
 | |
| 		if (!_working()) return false;
 | |
| 	}
 | |
| 
 | |
| 	// detect order of read attempts
 | |
| 	QString toTry[2];
 | |
| 	toTry[0] = ((options & FileOption::User) ? _userBasePath : _basePath) + name + '0';
 | |
| 	if (options & FileOption::Safe) {
 | |
| 		QFileInfo toTry0(toTry[0]);
 | |
| 		if (toTry0.exists()) {
 | |
| 			toTry[1] = ((options & FileOption::User) ? _userBasePath : _basePath) + name + '1';
 | |
| 			QFileInfo toTry1(toTry[1]);
 | |
| 			if (toTry1.exists()) {
 | |
| 				QDateTime mod0 = toTry0.lastModified(), mod1 = toTry1.lastModified();
 | |
| 				if (mod0 < mod1) {
 | |
| 					qSwap(toTry[0], toTry[1]);
 | |
| 				}
 | |
| 			} else {
 | |
| 				toTry[1] = QString();
 | |
| 			}
 | |
| 		} else {
 | |
| 			toTry[0][toTry[0].size() - 1] = '1';
 | |
| 		}
 | |
| 	}
 | |
| 	for (int32 i = 0; i < 2; ++i) {
 | |
| 		QString fname(toTry[i]);
 | |
| 		if (fname.isEmpty()) break;
 | |
| 
 | |
| 		QFile f(fname);
 | |
| 		if (!f.open(QIODevice::ReadOnly)) {
 | |
| 			DEBUG_LOG(("App Info: failed to open '%1' for reading").arg(name));
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		// check magic
 | |
| 		char magic[tdfMagicLen];
 | |
| 		if (f.read(magic, tdfMagicLen) != tdfMagicLen) {
 | |
| 			DEBUG_LOG(("App Info: failed to read magic from '%1'").arg(name));
 | |
| 			continue;
 | |
| 		}
 | |
| 		if (memcmp(magic, tdfMagic, tdfMagicLen)) {
 | |
| 			DEBUG_LOG(("App Info: bad magic %1 in '%2'").arg(Logs::mb(magic, tdfMagicLen).str()).arg(name));
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		// read app version
 | |
| 		qint32 version;
 | |
| 		if (f.read((char*)&version, sizeof(version)) != sizeof(version)) {
 | |
| 			DEBUG_LOG(("App Info: failed to read version from '%1'").arg(name));
 | |
| 			continue;
 | |
| 		}
 | |
| 		if (version > AppVersion) {
 | |
| 			DEBUG_LOG(("App Info: version too big %1 for '%2', my version %3").arg(version).arg(name).arg(AppVersion));
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		// read data
 | |
| 		QByteArray bytes = f.read(f.size());
 | |
| 		int32 dataSize = bytes.size() - 16;
 | |
| 		if (dataSize < 0) {
 | |
| 			DEBUG_LOG(("App Info: bad file '%1', could not read sign part").arg(name));
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		// check signature
 | |
| 		HashMd5 md5;
 | |
| 		md5.feed(bytes.constData(), dataSize);
 | |
| 		md5.feed(&dataSize, sizeof(dataSize));
 | |
| 		md5.feed(&version, sizeof(version));
 | |
| 		md5.feed(magic, tdfMagicLen);
 | |
| 		if (memcmp(md5.result(), bytes.constData() + dataSize, 16)) {
 | |
| 			DEBUG_LOG(("App Info: bad file '%1', signature did not match").arg(name));
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		bytes.resize(dataSize);
 | |
| 		result.data = bytes;
 | |
| 		bytes = QByteArray();
 | |
| 
 | |
| 		result.version = version;
 | |
| 		result.buffer.setBuffer(&result.data);
 | |
| 		result.buffer.open(QIODevice::ReadOnly);
 | |
| 		result.stream.setDevice(&result.buffer);
 | |
| 		result.stream.setVersion(QDataStream::Qt_5_1);
 | |
| 
 | |
| 		if ((i == 0 && !toTry[1].isEmpty()) || i == 1) {
 | |
| 			QFile::remove(toTry[1 - i]);
 | |
| 		}
 | |
| 
 | |
| 		return true;
 | |
| 	}
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| bool decryptLocal(EncryptedDescriptor &result, const QByteArray &encrypted, const MTP::AuthKeyPtr &key = LocalKey) {
 | |
| 	if (encrypted.size() <= 16 || (encrypted.size() & 0x0F)) {
 | |
| 		LOG(("App Error: bad encrypted part size: %1").arg(encrypted.size()));
 | |
| 		return false;
 | |
| 	}
 | |
| 	uint32 fullLen = encrypted.size() - 16;
 | |
| 
 | |
| 	QByteArray decrypted;
 | |
| 	decrypted.resize(fullLen);
 | |
| 	const char *encryptedKey = encrypted.constData(), *encryptedData = encrypted.constData() + 16;
 | |
| 	aesDecryptLocal(encryptedData, decrypted.data(), fullLen, key, encryptedKey);
 | |
| 	uchar sha1Buffer[20];
 | |
| 	if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), encryptedKey, 16)) {
 | |
| 		LOG(("App Info: bad decrypt key, data not decrypted - incorrect password?"));
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	uint32 dataLen = *(const uint32*)decrypted.constData();
 | |
| 	if (dataLen > uint32(decrypted.size()) || dataLen <= fullLen - 16 || dataLen < sizeof(uint32)) {
 | |
| 		LOG(("App Error: bad decrypted part size: %1, fullLen: %2, decrypted size: %3").arg(dataLen).arg(fullLen).arg(decrypted.size()));
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	decrypted.resize(dataLen);
 | |
| 	result.data = decrypted;
 | |
| 	decrypted = QByteArray();
 | |
| 
 | |
| 	result.buffer.setBuffer(&result.data);
 | |
| 	result.buffer.open(QIODevice::ReadOnly);
 | |
| 	result.buffer.seek(sizeof(uint32)); // skip len
 | |
| 	result.stream.setDevice(&result.buffer);
 | |
| 	result.stream.setVersion(QDataStream::Qt_5_1);
 | |
| 
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| bool readEncryptedFile(FileReadDescriptor &result, const QString &name, FileOptions options = FileOption::User | FileOption::Safe, const MTP::AuthKeyPtr &key = LocalKey) {
 | |
| 	if (!readFile(result, name, options)) {
 | |
| 		return false;
 | |
| 	}
 | |
| 	QByteArray encrypted;
 | |
| 	result.stream >> encrypted;
 | |
| 
 | |
| 	EncryptedDescriptor data;
 | |
| 	if (!decryptLocal(data, encrypted, key)) {
 | |
| 		result.stream.setDevice(nullptr);
 | |
| 		if (result.buffer.isOpen()) result.buffer.close();
 | |
| 		result.buffer.setBuffer(nullptr);
 | |
| 		result.data = QByteArray();
 | |
| 		result.version = 0;
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	result.stream.setDevice(0);
 | |
| 	if (result.buffer.isOpen()) result.buffer.close();
 | |
| 	result.buffer.setBuffer(0);
 | |
| 	result.data = data.data;
 | |
| 	result.buffer.setBuffer(&result.data);
 | |
| 	result.buffer.open(QIODevice::ReadOnly);
 | |
| 	result.buffer.seek(data.buffer.pos());
 | |
| 	result.stream.setDevice(&result.buffer);
 | |
| 	result.stream.setVersion(QDataStream::Qt_5_1);
 | |
| 
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| bool readEncryptedFile(FileReadDescriptor &result, const FileKey &fkey, FileOptions options = FileOption::User | FileOption::Safe, const MTP::AuthKeyPtr &key = LocalKey) {
 | |
| 	return readEncryptedFile(result, toFilePart(fkey), options, key);
 | |
| }
 | |
| 
 | |
| FileKey _dataNameKey = 0;
 | |
| 
 | |
| 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
 | |
| 	lskBackgroundOld = 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
 | |
| 	lskBackground = 0x14, // no data
 | |
| 	lskSelfSerialized = 0x15, // serialized self
 | |
| };
 | |
| 
 | |
| enum {
 | |
| 	dbiKey = 0x00,
 | |
| 	dbiUser = 0x01,
 | |
| 	dbiDcOptionOldOld = 0x02,
 | |
| 	dbiChatSizeMax = 0x03,
 | |
| 	dbiMutePeer = 0x04,
 | |
| 	dbiSendKeyOld = 0x05,
 | |
| 	dbiAutoStart = 0x06,
 | |
| 	dbiStartMinimized = 0x07,
 | |
| 	dbiSoundNotify = 0x08,
 | |
| 	dbiWorkMode = 0x09,
 | |
| 	dbiSeenTrayTooltip = 0x0a,
 | |
| 	dbiDesktopNotify = 0x0b,
 | |
| 	dbiAutoUpdate = 0x0c,
 | |
| 	dbiLastUpdateCheck = 0x0d,
 | |
| 	dbiWindowPosition = 0x0e,
 | |
| 	dbiConnectionTypeOld = 0x0f,
 | |
| 	// 0x10 reserved
 | |
| 	dbiDefaultAttach = 0x11,
 | |
| 	dbiCatsAndDogs = 0x12,
 | |
| 	dbiReplaceEmojiOld = 0x13,
 | |
| 	dbiAskDownloadPath = 0x14,
 | |
| 	dbiDownloadPathOld = 0x15,
 | |
| 	dbiScaleOld = 0x16,
 | |
| 	dbiEmojiTabOld = 0x17,
 | |
| 	dbiRecentEmojiOldOld = 0x18,
 | |
| 	dbiLoggedPhoneNumber = 0x19,
 | |
| 	dbiMutedPeers = 0x1a,
 | |
| 	// 0x1b reserved
 | |
| 	dbiNotifyView = 0x1c,
 | |
| 	dbiSendToMenu = 0x1d,
 | |
| 	dbiCompressPastedImage = 0x1e,
 | |
| 	dbiLangOld = 0x1f,
 | |
| 	dbiLangFileOld = 0x20,
 | |
| 	dbiTileBackgroundOld = 0x21,
 | |
| 	dbiAutoLock = 0x22,
 | |
| 	dbiDialogLastPath = 0x23,
 | |
| 	dbiRecentEmojiOld = 0x24,
 | |
| 	dbiEmojiVariantsOld = 0x25,
 | |
| 	dbiRecentStickers = 0x26,
 | |
| 	dbiDcOptionOld = 0x27,
 | |
| 	dbiTryIPv6 = 0x28,
 | |
| 	dbiSongVolume = 0x29,
 | |
| 	dbiWindowsNotificationsOld = 0x30,
 | |
| 	dbiIncludeMutedOld = 0x31,
 | |
| 	dbiMegagroupSizeMax = 0x32,
 | |
| 	dbiDownloadPath = 0x33,
 | |
| 	dbiAutoDownloadOld = 0x34,
 | |
| 	dbiSavedGifsLimit = 0x35,
 | |
| 	dbiShowingSavedGifsOld = 0x36,
 | |
| 	dbiAutoPlayOld = 0x37,
 | |
| 	dbiAdaptiveForWide = 0x38,
 | |
| 	dbiHiddenPinnedMessages = 0x39,
 | |
| 	dbiRecentEmoji = 0x3a,
 | |
| 	dbiEmojiVariants = 0x3b,
 | |
| 	dbiDialogsMode = 0x40,
 | |
| 	dbiModerateMode = 0x41,
 | |
| 	dbiVideoVolume = 0x42,
 | |
| 	dbiStickersRecentLimit = 0x43,
 | |
| 	dbiNativeNotifications = 0x44,
 | |
| 	dbiNotificationsCount  = 0x45,
 | |
| 	dbiNotificationsCorner = 0x46,
 | |
| 	dbiThemeKeyOld = 0x47,
 | |
| 	dbiDialogsWidthRatioOld = 0x48,
 | |
| 	dbiUseExternalVideoPlayer = 0x49,
 | |
| 	dbiDcOptions = 0x4a,
 | |
| 	dbiMtpAuthorization = 0x4b,
 | |
| 	dbiLastSeenWarningSeenOld = 0x4c,
 | |
| 	dbiSessionSettings = 0x4d,
 | |
| 	dbiLangPackKey = 0x4e,
 | |
| 	dbiConnectionType = 0x4f,
 | |
| 	dbiStickersFavedLimit = 0x50,
 | |
| 	dbiSuggestStickersByEmojiOld = 0x51,
 | |
| 	dbiSuggestEmojiOld = 0x52,
 | |
| 	dbiTxtDomainStringOld = 0x53,
 | |
| 	dbiThemeKey = 0x54,
 | |
| 	dbiTileBackground = 0x55,
 | |
| 	dbiCacheSettingsOld = 0x56,
 | |
| 	dbiAnimationsDisabled = 0x57,
 | |
| 	dbiScalePercent = 0x58,
 | |
| 	dbiPlaybackSpeed = 0x59,
 | |
| 	dbiLanguagesKey = 0x5a,
 | |
| 	dbiCallSettings = 0x5b,
 | |
| 	dbiCacheSettings = 0x5c,
 | |
| 	dbiTxtDomainString = 0x5d,
 | |
| 	dbiApplicationSettings = 0x5e,
 | |
| 
 | |
| 	dbiEncryptedWithSalt = 333,
 | |
| 	dbiEncrypted = 444,
 | |
| 
 | |
| 	// 500-600 reserved
 | |
| 
 | |
| 	dbiVersion = 666,
 | |
| };
 | |
| 
 | |
| enum {
 | |
| 	dbictAuto = 0,
 | |
| 	dbictHttpAuto = 1, // not used
 | |
| 	dbictHttpProxy = 2,
 | |
| 	dbictTcpProxy = 3,
 | |
| 	dbictProxiesListOld = 4,
 | |
| 	dbictProxiesList = 5,
 | |
| };
 | |
| 
 | |
| typedef QMap<PeerId, FileKey> DraftsMap;
 | |
| DraftsMap _draftsMap, _draftCursorsMap;
 | |
| typedef QMap<PeerId, bool> DraftsNotReadMap;
 | |
| DraftsNotReadMap _draftsNotReadMap;
 | |
| 
 | |
| typedef QPair<FileKey, qint32> FileDesc; // file, size
 | |
| 
 | |
| typedef QMultiMap<MediaKey, FileLocation> FileLocations;
 | |
| FileLocations _fileLocations;
 | |
| typedef QPair<MediaKey, FileLocation> FileLocationPair;
 | |
| typedef QMap<QString, FileLocationPair> FileLocationPairs;
 | |
| FileLocationPairs _fileLocationPairs;
 | |
| typedef QMap<MediaKey, MediaKey> FileLocationAliases;
 | |
| FileLocationAliases _fileLocationAliases;
 | |
| FileKey _locationsKey = 0, _trustedBotsKey = 0;
 | |
| 
 | |
| using TrustedBots = OrderedSet<uint64>;
 | |
| TrustedBots _trustedBots;
 | |
| bool _trustedBotsRead = false;
 | |
| 
 | |
| FileKey _recentStickersKeyOld = 0;
 | |
| FileKey _installedStickersKey = 0, _featuredStickersKey = 0, _recentStickersKey = 0, _favedStickersKey = 0, _archivedStickersKey = 0;
 | |
| FileKey _savedGifsKey = 0;
 | |
| 
 | |
| FileKey _backgroundKeyDay = 0;
 | |
| FileKey _backgroundKeyNight = 0;
 | |
| bool _backgroundCanWrite = true;
 | |
| 
 | |
| FileKey _themeKeyDay = 0;
 | |
| FileKey _themeKeyNight = 0;
 | |
| 
 | |
| // Theme key legacy may be read in start() with settings.
 | |
| // But it should be moved to keyDay or keyNight inside InitialLoadTheme()
 | |
| // and never used after.
 | |
| FileKey _themeKeyLegacy = 0;
 | |
| 
 | |
| bool _readingUserSettings = false;
 | |
| FileKey _userSettingsKey = 0;
 | |
| FileKey _recentHashtagsAndBotsKey = 0;
 | |
| bool _recentHashtagsAndBotsWereRead = false;
 | |
| qint64 _cacheTotalSizeLimit = Database::Settings().totalSizeLimit;
 | |
| qint32 _cacheTotalTimeLimit = Database::Settings().totalTimeLimit;
 | |
| qint64 _cacheBigFileTotalSizeLimit = Database::Settings().totalSizeLimit;
 | |
| qint32 _cacheBigFileTotalTimeLimit = Database::Settings().totalTimeLimit;
 | |
| 
 | |
| bool NoTimeLimit(qint32 storedLimitValue) {
 | |
| 	// This is a workaround for a bug in storing the cache time limit.
 | |
| 	// See https://github.com/telegramdesktop/tdesktop/issues/5611
 | |
| 	return !storedLimitValue
 | |
| 		|| (storedLimitValue == qint32(std::numeric_limits<int32>::max()))
 | |
| 		|| (storedLimitValue == qint32(std::numeric_limits<int64>::max()));
 | |
| }
 | |
| 
 | |
| FileKey _exportSettingsKey = 0;
 | |
| 
 | |
| FileKey _langPackKey = 0;
 | |
| FileKey _languagesKey = 0;
 | |
| 
 | |
| bool _mapChanged = false;
 | |
| int32 _oldMapVersion = 0, _oldSettingsVersion = 0;
 | |
| 
 | |
| enum class WriteMapWhen {
 | |
| 	Now,
 | |
| 	Fast,
 | |
| 	Soon,
 | |
| };
 | |
| 
 | |
| std::unique_ptr<Main::Settings> StoredSessionSettings;
 | |
| Main::Settings &GetStoredSessionSettings() {
 | |
| 	if (!StoredSessionSettings) {
 | |
| 		StoredSessionSettings = std::make_unique<Main::Settings>();
 | |
| 	}
 | |
| 	return *StoredSessionSettings;
 | |
| }
 | |
| 
 | |
| void _writeMap(WriteMapWhen when = WriteMapWhen::Soon);
 | |
| 
 | |
| void _writeLocations(WriteMapWhen when = WriteMapWhen::Soon) {
 | |
| 	Expects(_manager != nullptr);
 | |
| 
 | |
| 	if (when != WriteMapWhen::Now) {
 | |
| 		_manager->writeLocations(when == WriteMapWhen::Fast);
 | |
| 		return;
 | |
| 	}
 | |
| 	if (!_working()) return;
 | |
| 
 | |
| 	_manager->writingLocations();
 | |
| 	if (_fileLocations.isEmpty()) {
 | |
| 		if (_locationsKey) {
 | |
| 			clearKey(_locationsKey);
 | |
| 			_locationsKey = 0;
 | |
| 			_mapChanged = true;
 | |
| 			_writeMap();
 | |
| 		}
 | |
| 	} else {
 | |
| 		if (!_locationsKey) {
 | |
| 			_locationsKey = genKey();
 | |
| 			_mapChanged = true;
 | |
| 			_writeMap(WriteMapWhen::Fast);
 | |
| 		}
 | |
| 		quint32 size = 0;
 | |
| 		for (FileLocations::const_iterator 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 (FileLocationAliases::const_iterator 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 (FileLocations::const_iterator 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 (FileLocationAliases::const_iterator 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);
 | |
| 		file.writeEncrypted(data);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void _readLocations() {
 | |
| 	FileReadDescriptor locations;
 | |
| 	if (!readEncryptedFile(locations, _locationsKey)) {
 | |
| 		clearKey(_locationsKey);
 | |
| 		_locationsKey = 0;
 | |
| 		_writeMap();
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	bool endMarkFound = false;
 | |
| 	while (!locations.stream.atEnd()) {
 | |
| 		quint64 first, second;
 | |
| 		QByteArray bookmark;
 | |
| 		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, FileLocationPair(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, FileOption::User);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| struct ReadSettingsContext {
 | |
| 	MTP::DcOptions dcOptions;
 | |
| };
 | |
| 
 | |
| void applyReadContext(ReadSettingsContext &&context) {
 | |
| 	Core::App().dcOptions()->addFromOther(std::move(context.dcOptions));
 | |
| }
 | |
| 
 | |
| QByteArray serializeCallSettings(){
 | |
| 	QByteArray result=QByteArray();
 | |
| 	uint32 size = 3*sizeof(qint32) + Serialize::stringSize(Global::CallOutputDeviceID()) + Serialize::stringSize(Global::CallInputDeviceID());
 | |
| 	result.reserve(size);
 | |
| 	QDataStream stream(&result, QIODevice::WriteOnly);
 | |
| 	stream.setVersion(QDataStream::Qt_5_1);
 | |
| 	stream << Global::CallOutputDeviceID();
 | |
| 	stream << qint32(Global::CallOutputVolume());
 | |
| 	stream << Global::CallInputDeviceID();
 | |
| 	stream << qint32(Global::CallInputVolume());
 | |
| 	stream << qint32(Global::CallAudioDuckingEnabled() ? 1 : 0);
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| void deserializeCallSettings(QByteArray& settings){
 | |
| 	QDataStream stream(&settings, QIODevice::ReadOnly);
 | |
| 	stream.setVersion(QDataStream::Qt_5_1);
 | |
| 	QString outputDeviceID;
 | |
| 	QString inputDeviceID;
 | |
| 	qint32 outputVolume;
 | |
| 	qint32 inputVolume;
 | |
| 	qint32 duckingEnabled;
 | |
| 
 | |
| 	stream >> outputDeviceID;
 | |
| 	stream >> outputVolume;
 | |
| 	stream >> inputDeviceID;
 | |
| 	stream >> inputVolume;
 | |
| 	stream >> duckingEnabled;
 | |
| 	if(_checkStreamStatus(stream)){
 | |
| 		Global::SetCallOutputDeviceID(outputDeviceID);
 | |
| 		Global::SetCallOutputVolume(outputVolume);
 | |
| 		Global::SetCallInputDeviceID(inputDeviceID);
 | |
| 		Global::SetCallInputVolume(inputVolume);
 | |
| 		Global::SetCallAudioDuckingEnabled(duckingEnabled);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSettingsContext &context) {
 | |
| 	switch (blockId) {
 | |
| 	case dbiDcOptionOldOld: {
 | |
| 		quint32 dcId, port;
 | |
| 		QString host, ip;
 | |
| 		stream >> dcId >> host >> ip >> port;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		context.dcOptions.constructAddOne(
 | |
| 			dcId,
 | |
| 			0,
 | |
| 			ip.toStdString(),
 | |
| 			port,
 | |
| 			{});
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiDcOptionOld: {
 | |
| 		quint32 dcIdWithShift, port;
 | |
| 		qint32 flags;
 | |
| 		QString ip;
 | |
| 		stream >> dcIdWithShift >> flags >> ip >> port;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		context.dcOptions.constructAddOne(
 | |
| 			dcIdWithShift,
 | |
| 			MTPDdcOption::Flags::from_raw(flags),
 | |
| 			ip.toStdString(),
 | |
| 			port,
 | |
| 			{});
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiDcOptions: {
 | |
| 		auto serialized = QByteArray();
 | |
| 		stream >> serialized;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		context.dcOptions.constructFromSerialized(serialized);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiApplicationSettings: {
 | |
| 		auto serialized = QByteArray();
 | |
| 		stream >> serialized;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		Core::App().settings().constructFromSerialized(serialized);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiChatSizeMax: {
 | |
| 		qint32 maxSize;
 | |
| 		stream >> maxSize;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		Global::SetChatSizeMax(maxSize);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiSavedGifsLimit: {
 | |
| 		qint32 limit;
 | |
| 		stream >> limit;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		Global::SetSavedGifsLimit(limit);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiStickersRecentLimit: {
 | |
| 		qint32 limit;
 | |
| 		stream >> limit;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		Global::SetStickersRecentLimit(limit);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiStickersFavedLimit: {
 | |
| 		qint32 limit;
 | |
| 		stream >> limit;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		Global::SetStickersFavedLimit(limit);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiMegagroupSizeMax: {
 | |
| 		qint32 maxSize;
 | |
| 		stream >> maxSize;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		Global::SetMegagroupSizeMax(maxSize);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiUser: {
 | |
| 		quint32 dcId;
 | |
| 		qint32 userId;
 | |
| 		stream >> userId >> dcId;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		DEBUG_LOG(("MTP Info: user found, dc %1, uid %2").arg(dcId).arg(userId));
 | |
| 		Core::App().activeAccount().setMtpMainDcId(dcId);
 | |
| 		Core::App().activeAccount().setSessionUserId(userId);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiKey: {
 | |
| 		qint32 dcId;
 | |
| 		stream >> dcId;
 | |
| 		auto key = Serialize::read<MTP::AuthKey::Data>(stream);
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		Core::App().activeAccount().setMtpKey(dcId, key);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiMtpAuthorization: {
 | |
| 		auto serialized = QByteArray();
 | |
| 		stream >> serialized;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		Core::App().activeAccount().setMtpAuthorization(serialized);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiAutoStart: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		cSetAutoStart(v == 1);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiStartMinimized: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		cSetStartMinimized(v == 1);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiSendToMenu: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		cSetSendToMenu(v == 1);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiUseExternalVideoPlayer: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		cSetUseExternalVideoPlayer(v == 1);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiCacheSettingsOld: {
 | |
| 		qint64 size;
 | |
| 		qint32 time;
 | |
| 		stream >> size >> time;
 | |
| 		if (!_checkStreamStatus(stream)
 | |
| 			|| size <= Database::Settings().maxDataSize
 | |
| 			|| (!NoTimeLimit(time) && time < 0)) {
 | |
| 			return false;
 | |
| 		}
 | |
| 		_cacheTotalSizeLimit = size;
 | |
| 		_cacheTotalTimeLimit = NoTimeLimit(time) ? 0 : time;
 | |
| 		_cacheBigFileTotalSizeLimit = size;
 | |
| 		_cacheBigFileTotalTimeLimit = NoTimeLimit(time) ? 0 : time;
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiCacheSettings: {
 | |
| 		qint64 size, sizeBig;
 | |
| 		qint32 time, timeBig;
 | |
| 		stream >> size >> time >> sizeBig >> timeBig;
 | |
| 		if (!_checkStreamStatus(stream)
 | |
| 			|| size <= Database::Settings().maxDataSize
 | |
| 			|| sizeBig <= Database::Settings().maxDataSize
 | |
| 			|| (!NoTimeLimit(time) && time < 0)
 | |
| 			|| (!NoTimeLimit(timeBig) && timeBig < 0)) {
 | |
| 			return false;
 | |
| 		}
 | |
| 
 | |
| 		_cacheTotalSizeLimit = size;
 | |
| 		_cacheTotalTimeLimit = NoTimeLimit(time) ? 0 : time;
 | |
| 		_cacheBigFileTotalSizeLimit = sizeBig;
 | |
| 		_cacheBigFileTotalTimeLimit = NoTimeLimit(timeBig) ? 0 : timeBig;
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiAnimationsDisabled: {
 | |
| 		qint32 disabled;
 | |
| 		stream >> disabled;
 | |
| 		if (!_checkStreamStatus(stream)) {
 | |
| 			return false;
 | |
| 		}
 | |
| 
 | |
| 		anim::SetDisabled(disabled == 1);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiSoundNotify: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		Global::SetSoundNotify(v == 1);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiAutoDownloadOld: {
 | |
| 		qint32 photo, audio, gif;
 | |
| 		stream >> photo >> audio >> gif;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		using namespace Data::AutoDownload;
 | |
| 		auto &settings = GetStoredSessionSettings().autoDownload();
 | |
| 		const auto disabled = [](qint32 value, qint32 mask) {
 | |
| 			return (value & mask) != 0;
 | |
| 		};
 | |
| 		const auto set = [&](Type type, qint32 value) {
 | |
| 			constexpr auto kNoPrivate = qint32(0x01);
 | |
| 			constexpr auto kNoGroups = qint32(0x02);
 | |
| 			if (disabled(value, kNoPrivate)) {
 | |
| 				settings.setBytesLimit(Source::User, type, 0);
 | |
| 			}
 | |
| 			if (disabled(value, kNoGroups)) {
 | |
| 				settings.setBytesLimit(Source::Group, type, 0);
 | |
| 				settings.setBytesLimit(Source::Channel, type, 0);
 | |
| 			}
 | |
| 		};
 | |
| 		set(Type::Photo, photo);
 | |
| 		set(Type::VoiceMessage, audio);
 | |
| 		set(Type::GIF, gif);
 | |
| 		set(Type::VideoMessage, gif);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiAutoPlayOld: {
 | |
| 		qint32 gif;
 | |
| 		stream >> gif;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		GetStoredSessionSettings().setAutoplayGifs(gif == 1);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiDialogsMode: {
 | |
| 		qint32 enabled, modeInt;
 | |
| 		stream >> enabled >> modeInt;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		Global::SetDialogsModeEnabled(enabled == 1);
 | |
| 		auto mode = Dialogs::Mode::All;
 | |
| 		if (enabled) {
 | |
| 			mode = static_cast<Dialogs::Mode>(modeInt);
 | |
| 			if (mode != Dialogs::Mode::All && mode != Dialogs::Mode::Important) {
 | |
| 				mode = Dialogs::Mode::All;
 | |
| 			}
 | |
| 		}
 | |
| 		Global::SetDialogsMode(mode);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiModerateMode: {
 | |
| 		qint32 enabled;
 | |
| 		stream >> enabled;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		Global::SetModerateModeEnabled(enabled == 1);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiIncludeMutedOld: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		GetStoredSessionSettings().setIncludeMutedCounter(v == 1);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiShowingSavedGifsOld: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiDesktopNotify: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		Global::SetDesktopNotify(v == 1);
 | |
| 		if (App::wnd()) App::wnd()->updateTrayMenu();
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiWindowsNotificationsOld: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiNativeNotifications: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		Global::SetNativeNotifications(v == 1);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiNotificationsCount: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		Global::SetNotificationsCount((v > 0 ? v : 3));
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiNotificationsCorner: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		Global::SetNotificationsCorner(static_cast<Notify::ScreenCorner>((v >= 0 && v < 4) ? v : 2));
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiDialogsWidthRatioOld: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		GetStoredSessionSettings().setDialogsWidthRatio(v / 1000000.);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiLastSeenWarningSeenOld: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		GetStoredSessionSettings().setLastSeenWarningSeen(v == 1);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiSessionSettings: {
 | |
| 		QByteArray v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		GetStoredSessionSettings().constructFromSerialized(v);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiWorkMode: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		auto newMode = [v] {
 | |
| 			switch (v) {
 | |
| 			case dbiwmTrayOnly: return dbiwmTrayOnly;
 | |
| 			case dbiwmWindowOnly: return dbiwmWindowOnly;
 | |
| 			};
 | |
| 			return dbiwmWindowAndTray;
 | |
| 		};
 | |
| 		Global::RefWorkMode().set(newMode());
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiTxtDomainStringOld: {
 | |
| 		QString v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiTxtDomainString: {
 | |
| 		QString v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		Global::SetTxtDomainString(v);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiConnectionTypeOld: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		MTP::ProxyData proxy;
 | |
| 		switch (v) {
 | |
| 		case dbictHttpProxy:
 | |
| 		case dbictTcpProxy: {
 | |
| 			qint32 port;
 | |
| 			stream >> proxy.host >> port >> proxy.user >> proxy.password;
 | |
| 			if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 			proxy.port = uint32(port);
 | |
| 			proxy.type = (v == dbictTcpProxy)
 | |
| 				? MTP::ProxyData::Type::Socks5
 | |
| 				: MTP::ProxyData::Type::Http;
 | |
| 		} break;
 | |
| 		};
 | |
| 		Global::SetSelectedProxy(proxy ? proxy : MTP::ProxyData());
 | |
| 		Global::SetProxySettings(proxy
 | |
| 			? MTP::ProxyData::Settings::Enabled
 | |
| 			: MTP::ProxyData::Settings::System);
 | |
| 		if (proxy) {
 | |
| 			Global::SetProxiesList({ 1, proxy });
 | |
| 		} else {
 | |
| 			Global::SetProxiesList({});
 | |
| 		}
 | |
| 		Core::App().refreshGlobalProxy();
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiConnectionType: {
 | |
| 		qint32 connectionType;
 | |
| 		stream >> connectionType;
 | |
| 		if (!_checkStreamStatus(stream)) {
 | |
| 			return false;
 | |
| 		}
 | |
| 
 | |
| 		const auto readProxy = [&] {
 | |
| 			qint32 proxyType, port;
 | |
| 			MTP::ProxyData proxy;
 | |
| 			stream >> proxyType >> proxy.host >> port >> proxy.user >> proxy.password;
 | |
| 			proxy.port = port;
 | |
| 			proxy.type = (proxyType == dbictTcpProxy)
 | |
| 				? MTP::ProxyData::Type::Socks5
 | |
| 				: (proxyType == dbictHttpProxy)
 | |
| 				? MTP::ProxyData::Type::Http
 | |
| 				: (proxyType == kProxyTypeShift + int(MTP::ProxyData::Type::Socks5))
 | |
| 				? MTP::ProxyData::Type::Socks5
 | |
| 				: (proxyType == kProxyTypeShift + int(MTP::ProxyData::Type::Http))
 | |
| 				? MTP::ProxyData::Type::Http
 | |
| 				: (proxyType == kProxyTypeShift + int(MTP::ProxyData::Type::Mtproto))
 | |
| 				? MTP::ProxyData::Type::Mtproto
 | |
| 				: MTP::ProxyData::Type::None;
 | |
| 			return proxy;
 | |
| 		};
 | |
| 		if (connectionType == dbictProxiesListOld
 | |
| 			|| connectionType == dbictProxiesList) {
 | |
| 			qint32 count = 0, index = 0;
 | |
| 			stream >> count >> index;
 | |
| 			qint32 settings = 0, calls = 0;
 | |
| 			if (connectionType == dbictProxiesList) {
 | |
| 				stream >> settings >> calls;
 | |
| 			} else if (std::abs(index) > count) {
 | |
| 				calls = 1;
 | |
| 				index -= (index > 0 ? count : -count);
 | |
| 			}
 | |
| 
 | |
| 			auto list = std::vector<MTP::ProxyData>();
 | |
| 			for (auto i = 0; i < count; ++i) {
 | |
| 				const auto proxy = readProxy();
 | |
| 				if (proxy) {
 | |
| 					list.push_back(proxy);
 | |
| 				} else if (index < -list.size()) {
 | |
| 					++index;
 | |
| 				} else if (index > list.size()) {
 | |
| 					--index;
 | |
| 				}
 | |
| 			}
 | |
| 			if (!_checkStreamStatus(stream)) {
 | |
| 				return false;
 | |
| 			}
 | |
| 			Global::SetProxiesList(list);
 | |
| 			if (connectionType == dbictProxiesListOld) {
 | |
| 				settings = static_cast<qint32>(
 | |
| 					(index > 0 && index <= list.size()
 | |
| 						? MTP::ProxyData::Settings::Enabled
 | |
| 						: MTP::ProxyData::Settings::System));
 | |
| 				index = std::abs(index);
 | |
| 			}
 | |
| 			if (index > 0 && index <= list.size()) {
 | |
| 				Global::SetSelectedProxy(list[index - 1]);
 | |
| 			} else {
 | |
| 				Global::SetSelectedProxy(MTP::ProxyData());
 | |
| 			}
 | |
| 
 | |
| 			const auto unchecked = static_cast<MTP::ProxyData::Settings>(settings);
 | |
| 			switch (unchecked) {
 | |
| 			case MTP::ProxyData::Settings::Enabled:
 | |
| 				Global::SetProxySettings(Global::SelectedProxy()
 | |
| 					? MTP::ProxyData::Settings::Enabled
 | |
| 					: MTP::ProxyData::Settings::System);
 | |
| 				break;
 | |
| 			case MTP::ProxyData::Settings::Disabled:
 | |
| 			case MTP::ProxyData::Settings::System:
 | |
| 				Global::SetProxySettings(unchecked);
 | |
| 				break;
 | |
| 			default:
 | |
| 				Global::SetProxySettings(MTP::ProxyData::Settings::System);
 | |
| 				break;
 | |
| 			}
 | |
| 			Global::SetUseProxyForCalls(calls == 1);
 | |
| 		} else {
 | |
| 			const auto proxy = readProxy();
 | |
| 			if (!_checkStreamStatus(stream)) {
 | |
| 				return false;
 | |
| 			}
 | |
| 			if (proxy) {
 | |
| 				Global::SetProxiesList({ 1, proxy });
 | |
| 				Global::SetSelectedProxy(proxy);
 | |
| 				if (connectionType == dbictTcpProxy
 | |
| 					|| connectionType == dbictHttpProxy) {
 | |
| 					Global::SetProxySettings(MTP::ProxyData::Settings::Enabled);
 | |
| 				} else {
 | |
| 					Global::SetProxySettings(MTP::ProxyData::Settings::System);
 | |
| 				}
 | |
| 			} else {
 | |
| 				Global::SetProxiesList({});
 | |
| 				Global::SetSelectedProxy(MTP::ProxyData());
 | |
| 				Global::SetProxySettings(MTP::ProxyData::Settings::System);
 | |
| 			}
 | |
| 		}
 | |
| 		Core::App().refreshGlobalProxy();
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiThemeKeyOld: {
 | |
| 		quint64 key = 0;
 | |
| 		stream >> key;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		_themeKeyLegacy = key;
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiThemeKey: {
 | |
| 		quint64 keyDay = 0, keyNight = 0;
 | |
| 		quint32 nightMode = 0;
 | |
| 		stream >> keyDay >> keyNight >> nightMode;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		_themeKeyDay = keyDay;
 | |
| 		_themeKeyNight = keyNight;
 | |
| 		Window::Theme::SetNightModeValue(nightMode == 1);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiLangPackKey: {
 | |
| 		quint64 langPackKey = 0;
 | |
| 		stream >> langPackKey;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		_langPackKey = langPackKey;
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiLanguagesKey: {
 | |
| 		quint64 languagesKey = 0;
 | |
| 		stream >> languagesKey;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		_languagesKey = languagesKey;
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiTryIPv6: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		Global::SetTryIPv6(v == 1);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiSeenTrayTooltip: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		cSetSeenTrayTooltip(v == 1);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiAutoUpdate: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		cSetAutoUpdate(v == 1);
 | |
| 		if (!Core::UpdaterDisabled() && !cAutoUpdate()) {
 | |
| 			Core::UpdateChecker().stop();
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiLastUpdateCheck: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		cSetLastUpdateCheck(v);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiScaleOld: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		SetScaleChecked([&] {
 | |
| 			constexpr auto kAuto = 0;
 | |
| 			constexpr auto kOne = 1;
 | |
| 			constexpr auto kOneAndQuarter = 2;
 | |
| 			constexpr auto kOneAndHalf = 3;
 | |
| 			constexpr auto kTwo = 4;
 | |
| 			switch (v) {
 | |
| 			case kAuto: return style::kScaleAuto;
 | |
| 			case kOne: return 100;
 | |
| 			case kOneAndQuarter: return 125;
 | |
| 			case kOneAndHalf: return 150;
 | |
| 			case kTwo: return 200;
 | |
| 			}
 | |
| 			return cConfigScale();
 | |
| 		}());
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiScalePercent: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		// If cConfigScale() has value then it was set via command line.
 | |
| 		if (cConfigScale() == style::kScaleAuto) {
 | |
| 			SetScaleChecked(v);
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiLangOld: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiLangFileOld: {
 | |
| 		QString v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiWindowPosition: {
 | |
| 		auto position = TWindowPos();
 | |
| 		stream >> position.x >> position.y >> position.w >> position.h;
 | |
| 		stream >> position.moncrc >> position.maximized;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		DEBUG_LOG(("Window Pos: Read from storage %1, %2, %3, %4 (maximized %5)").arg(position.x).arg(position.y).arg(position.w).arg(position.h).arg(Logs::b(position.maximized)));
 | |
| 		cSetWindowPos(position);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiLoggedPhoneNumber: {
 | |
| 		QString v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		cSetLoggedPhoneNumber(v);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiMutePeer: { // deprecated
 | |
| 		quint64 peerId;
 | |
| 		stream >> peerId;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiMutedPeers: { // deprecated
 | |
| 		quint32 count;
 | |
| 		stream >> count;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		for (uint32 i = 0; i < count; ++i) {
 | |
| 			quint64 peerId;
 | |
| 			stream >> peerId;
 | |
| 		}
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiSendKeyOld: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		using SendSettings = Ui::InputSubmitSettings;
 | |
| 		const auto unchecked = static_cast<SendSettings>(v);
 | |
| 
 | |
| 		if (unchecked != SendSettings::Enter
 | |
| 			&& unchecked != SendSettings::CtrlEnter) {
 | |
| 			return false;
 | |
| 		}
 | |
| 		GetStoredSessionSettings().setSendSubmitWay(unchecked);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiCatsAndDogs: { // deprecated
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiTileBackgroundOld: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		bool tile = (version < 8005 && !_backgroundKeyDay)
 | |
| 			? false
 | |
| 			: (v == 1);
 | |
| 		if (Window::Theme::IsNightMode()) {
 | |
| 			Window::Theme::Background()->setTileNightValue(tile);
 | |
| 		} else {
 | |
| 			Window::Theme::Background()->setTileDayValue(tile);
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiTileBackground: {
 | |
| 		qint32 tileDay, tileNight;
 | |
| 		stream >> tileDay >> tileNight;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		Window::Theme::Background()->setTileDayValue(tileDay == 1);
 | |
| 		Window::Theme::Background()->setTileNightValue(tileNight == 1);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiAdaptiveForWide: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		Global::SetAdaptiveForWide(v == 1);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiAutoLock: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		Global::SetAutoLock(v);
 | |
| 		Global::RefLocalPasscodeChanged().notify();
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiReplaceEmojiOld: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		GetStoredSessionSettings().setReplaceEmoji(v == 1);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiSuggestEmojiOld: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		GetStoredSessionSettings().setSuggestEmoji(v == 1);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiSuggestStickersByEmojiOld: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		GetStoredSessionSettings().setSuggestStickersByEmoji(v == 1);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiDefaultAttach: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiNotifyView: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		switch (v) {
 | |
| 		case dbinvShowNothing: Global::SetNotifyView(dbinvShowNothing); break;
 | |
| 		case dbinvShowName: Global::SetNotifyView(dbinvShowName); break;
 | |
| 		default: Global::SetNotifyView(dbinvShowPreview); break;
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiAskDownloadPath: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		Global::SetAskDownloadPath(v == 1);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiDownloadPathOld: {
 | |
| 		QString v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| #ifndef OS_WIN_STORE
 | |
| 		if (!v.isEmpty() && v != qstr("tmp") && !v.endsWith('/')) v += '/';
 | |
| 		Global::SetDownloadPath(v);
 | |
| 		Global::SetDownloadPathBookmark(QByteArray());
 | |
| 		Global::RefDownloadPathChanged().notify();
 | |
| #endif // OS_WIN_STORE
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiDownloadPath: {
 | |
| 		QString v;
 | |
| 		QByteArray bookmark;
 | |
| 		stream >> v >> bookmark;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| #ifndef OS_WIN_STORE
 | |
| 		if (!v.isEmpty() && v != qstr("tmp") && !v.endsWith('/')) v += '/';
 | |
| 		Global::SetDownloadPath(v);
 | |
| 		Global::SetDownloadPathBookmark(bookmark);
 | |
| 		psDownloadPathEnableAccess();
 | |
| 		Global::RefDownloadPathChanged().notify();
 | |
| #endif // OS_WIN_STORE
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiCompressPastedImage: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		GetStoredSessionSettings().setSendFilesWay((v == 1)
 | |
| 			? SendFilesWay::Album
 | |
| 			: SendFilesWay::Files);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiEmojiTabOld: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		// deprecated
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiRecentEmojiOldOld: {
 | |
| 		RecentEmojiPreloadOldOld v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		if (!v.isEmpty()) {
 | |
| 			RecentEmojiPreload p;
 | |
| 			p.reserve(v.size());
 | |
| 			for (auto &item : v) {
 | |
| 				auto oldKey = uint64(item.first);
 | |
| 				switch (oldKey) {
 | |
| 				case 0xD83CDDEFLLU: oldKey = 0xD83CDDEFD83CDDF5LLU; break;
 | |
| 				case 0xD83CDDF0LLU: oldKey = 0xD83CDDF0D83CDDF7LLU; break;
 | |
| 				case 0xD83CDDE9LLU: oldKey = 0xD83CDDE9D83CDDEALLU; break;
 | |
| 				case 0xD83CDDE8LLU: oldKey = 0xD83CDDE8D83CDDF3LLU; break;
 | |
| 				case 0xD83CDDFALLU: oldKey = 0xD83CDDFAD83CDDF8LLU; break;
 | |
| 				case 0xD83CDDEBLLU: oldKey = 0xD83CDDEBD83CDDF7LLU; break;
 | |
| 				case 0xD83CDDEALLU: oldKey = 0xD83CDDEAD83CDDF8LLU; break;
 | |
| 				case 0xD83CDDEELLU: oldKey = 0xD83CDDEED83CDDF9LLU; break;
 | |
| 				case 0xD83CDDF7LLU: oldKey = 0xD83CDDF7D83CDDFALLU; break;
 | |
| 				case 0xD83CDDECLLU: oldKey = 0xD83CDDECD83CDDE7LLU; break;
 | |
| 				}
 | |
| 				auto id = Ui::Emoji::IdFromOldKey(oldKey);
 | |
| 				if (!id.isEmpty()) {
 | |
| 					p.push_back(qMakePair(id, item.second));
 | |
| 				}
 | |
| 			}
 | |
| 			cSetRecentEmojiPreload(p);
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiRecentEmojiOld: {
 | |
| 		RecentEmojiPreloadOld v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		if (!v.isEmpty()) {
 | |
| 			RecentEmojiPreload p;
 | |
| 			p.reserve(v.size());
 | |
| 			for (auto &item : v) {
 | |
| 				auto id = Ui::Emoji::IdFromOldKey(item.first);
 | |
| 				if (!id.isEmpty()) {
 | |
| 					p.push_back(qMakePair(id, item.second));
 | |
| 				}
 | |
| 			}
 | |
| 			cSetRecentEmojiPreload(p);
 | |
| 		}
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiRecentEmoji: {
 | |
| 		RecentEmojiPreload v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		cSetRecentEmojiPreload(v);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiRecentStickers: {
 | |
| 		RecentStickerPreload v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		cSetRecentStickersPreload(v);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiEmojiVariantsOld: {
 | |
| 		EmojiColorVariantsOld v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		EmojiColorVariants variants;
 | |
| 		for (auto i = v.cbegin(), e = v.cend(); i != e; ++i) {
 | |
| 			auto id = Ui::Emoji::IdFromOldKey(static_cast<uint64>(i.key()));
 | |
| 			if (!id.isEmpty()) {
 | |
| 				auto index = Ui::Emoji::ColorIndexFromOldKey(i.value());
 | |
| 				if (index >= 0) {
 | |
| 					variants.insert(id, index);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		cSetEmojiVariants(variants);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiEmojiVariants: {
 | |
| 		EmojiColorVariants v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		cSetEmojiVariants(v);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiHiddenPinnedMessages: {
 | |
| 		Global::HiddenPinnedMessagesMap v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		Global::SetHiddenPinnedMessages(v);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiDialogLastPath: {
 | |
| 		QString path;
 | |
| 		stream >> path;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		cSetDialogLastPath(path);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiSongVolume: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		Global::SetSongVolume(snap(v / 1e6, 0., 1.));
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiVideoVolume: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		Global::SetVideoVolume(snap(v / 1e6, 0., 1.));
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiPlaybackSpeed: {
 | |
| 		qint32 v;
 | |
| 		stream >> v;
 | |
| 		if (!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		Global::SetVoiceMsgPlaybackDoubled(v == 2);
 | |
| 	} break;
 | |
| 
 | |
| 	case dbiCallSettings: {
 | |
| 		QByteArray callSettings;
 | |
| 		stream >> callSettings;
 | |
| 		if(!_checkStreamStatus(stream)) return false;
 | |
| 
 | |
| 		deserializeCallSettings(callSettings);
 | |
| 	} break;
 | |
| 
 | |
| 	default:
 | |
| 	LOG(("App Error: unknown blockId in _readSetting: %1").arg(blockId));
 | |
| 	return false;
 | |
| 	}
 | |
| 
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| bool _readOldSettings(bool remove, ReadSettingsContext &context) {
 | |
| 	bool result = false;
 | |
| 	QFile file(cWorkingDir() + qsl("tdata/config"));
 | |
| 	if (file.open(QIODevice::ReadOnly)) {
 | |
| 		LOG(("App Info: reading old config..."));
 | |
| 		QDataStream stream(&file);
 | |
| 		stream.setVersion(QDataStream::Qt_5_1);
 | |
| 
 | |
| 		qint32 version = 0;
 | |
| 		while (!stream.atEnd()) {
 | |
| 			quint32 blockId;
 | |
| 			stream >> blockId;
 | |
| 			if (!_checkStreamStatus(stream)) break;
 | |
| 
 | |
| 			if (blockId == dbiVersion) {
 | |
| 				stream >> version;
 | |
| 				if (!_checkStreamStatus(stream)) break;
 | |
| 
 | |
| 				if (version > AppVersion) break;
 | |
| 			} else if (!_readSetting(blockId, stream, version, context)) {
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 		file.close();
 | |
| 		result = true;
 | |
| 	}
 | |
| 	if (remove) file.remove();
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| void _readOldUserSettingsFields(QIODevice *device, qint32 &version, ReadSettingsContext &context) {
 | |
| 	QDataStream stream(device);
 | |
| 	stream.setVersion(QDataStream::Qt_5_1);
 | |
| 
 | |
| 	while (!stream.atEnd()) {
 | |
| 		quint32 blockId;
 | |
| 		stream >> blockId;
 | |
| 		if (!_checkStreamStatus(stream)) {
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		if (blockId == dbiVersion) {
 | |
| 			stream >> version;
 | |
| 			if (!_checkStreamStatus(stream)) {
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			if (version > AppVersion) return;
 | |
| 		} else if (blockId == dbiEncryptedWithSalt) {
 | |
| 			QByteArray salt, data, decrypted;
 | |
| 			stream >> salt >> data;
 | |
| 			if (!_checkStreamStatus(stream)) {
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			if (salt.size() != 32) {
 | |
| 				LOG(("App Error: bad salt in old user config encrypted part, size: %1").arg(salt.size()));
 | |
| 				continue;
 | |
| 			}
 | |
| 
 | |
| 			createLocalKey(QByteArray(), &salt, &OldKey);
 | |
| 
 | |
| 			if (data.size() <= 16 || (data.size() & 0x0F)) {
 | |
| 				LOG(("App Error: bad encrypted part size in old user config: %1").arg(data.size()));
 | |
| 				continue;
 | |
| 			}
 | |
| 			uint32 fullDataLen = data.size() - 16;
 | |
| 			decrypted.resize(fullDataLen);
 | |
| 			const char *dataKey = data.constData(), *encrypted = data.constData() + 16;
 | |
| 			aesDecryptLocal(encrypted, decrypted.data(), fullDataLen, OldKey, dataKey);
 | |
| 			uchar sha1Buffer[20];
 | |
| 			if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), dataKey, 16)) {
 | |
| 				LOG(("App Error: bad decrypt key, data from old user config not decrypted"));
 | |
| 				continue;
 | |
| 			}
 | |
| 			uint32 dataLen = *(const uint32*)decrypted.constData();
 | |
| 			if (dataLen > uint32(decrypted.size()) || dataLen <= fullDataLen - 16 || dataLen < 4) {
 | |
| 				LOG(("App Error: bad decrypted part size in old user config: %1, fullDataLen: %2, decrypted size: %3").arg(dataLen).arg(fullDataLen).arg(decrypted.size()));
 | |
| 				continue;
 | |
| 			}
 | |
| 			decrypted.resize(dataLen);
 | |
| 			QBuffer decryptedStream(&decrypted);
 | |
| 			decryptedStream.open(QIODevice::ReadOnly);
 | |
| 			decryptedStream.seek(4); // skip size
 | |
| 			LOG(("App Info: reading encrypted old user config..."));
 | |
| 
 | |
| 			_readOldUserSettingsFields(&decryptedStream, version, context);
 | |
| 		} else if (!_readSetting(blockId, stream, version, context)) {
 | |
| 			return;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool _readOldUserSettings(bool remove, ReadSettingsContext &context) {
 | |
| 	bool result = false;
 | |
| 	QFile file(cWorkingDir() + cDataFile() + (cTestMode() ? qsl("_test") : QString()) + qsl("_config"));
 | |
| 	if (file.open(QIODevice::ReadOnly)) {
 | |
| 		LOG(("App Info: reading old user config..."));
 | |
| 		qint32 version = 0;
 | |
| 		_readOldUserSettingsFields(&file, version, context);
 | |
| 		file.close();
 | |
| 		result = true;
 | |
| 	}
 | |
| 	if (remove) file.remove();
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| void _readOldMtpDataFields(QIODevice *device, qint32 &version, ReadSettingsContext &context) {
 | |
| 	QDataStream stream(device);
 | |
| 	stream.setVersion(QDataStream::Qt_5_1);
 | |
| 
 | |
| 	while (!stream.atEnd()) {
 | |
| 		quint32 blockId;
 | |
| 		stream >> blockId;
 | |
| 		if (!_checkStreamStatus(stream)) {
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		if (blockId == dbiVersion) {
 | |
| 			stream >> version;
 | |
| 			if (!_checkStreamStatus(stream)) {
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			if (version > AppVersion) return;
 | |
| 		} else if (blockId == dbiEncrypted) {
 | |
| 			QByteArray data, decrypted;
 | |
| 			stream >> data;
 | |
| 			if (!_checkStreamStatus(stream)) {
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			if (!OldKey) {
 | |
| 				LOG(("MTP Error: reading old encrypted keys without old key!"));
 | |
| 				continue;
 | |
| 			}
 | |
| 
 | |
| 			if (data.size() <= 16 || (data.size() & 0x0F)) {
 | |
| 				LOG(("MTP Error: bad encrypted part size in old keys: %1").arg(data.size()));
 | |
| 				continue;
 | |
| 			}
 | |
| 			uint32 fullDataLen = data.size() - 16;
 | |
| 			decrypted.resize(fullDataLen);
 | |
| 			const char *dataKey = data.constData(), *encrypted = data.constData() + 16;
 | |
| 			aesDecryptLocal(encrypted, decrypted.data(), fullDataLen, OldKey, dataKey);
 | |
| 			uchar sha1Buffer[20];
 | |
| 			if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), dataKey, 16)) {
 | |
| 				LOG(("MTP Error: bad decrypt key, data from old keys not decrypted"));
 | |
| 				continue;
 | |
| 			}
 | |
| 			uint32 dataLen = *(const uint32*)decrypted.constData();
 | |
| 			if (dataLen > uint32(decrypted.size()) || dataLen <= fullDataLen - 16 || dataLen < 4) {
 | |
| 				LOG(("MTP Error: bad decrypted part size in old keys: %1, fullDataLen: %2, decrypted size: %3").arg(dataLen).arg(fullDataLen).arg(decrypted.size()));
 | |
| 				continue;
 | |
| 			}
 | |
| 			decrypted.resize(dataLen);
 | |
| 			QBuffer decryptedStream(&decrypted);
 | |
| 			decryptedStream.open(QIODevice::ReadOnly);
 | |
| 			decryptedStream.seek(4); // skip size
 | |
| 			LOG(("App Info: reading encrypted old keys..."));
 | |
| 
 | |
| 			_readOldMtpDataFields(&decryptedStream, version, context);
 | |
| 		} else if (!_readSetting(blockId, stream, version, context)) {
 | |
| 			return;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool _readOldMtpData(bool remove, ReadSettingsContext &context) {
 | |
| 	bool result = false;
 | |
| 	QFile file(cWorkingDir() + cDataFile() + (cTestMode() ? qsl("_test") : QString()));
 | |
| 	if (file.open(QIODevice::ReadOnly)) {
 | |
| 		LOG(("App Info: reading old keys..."));
 | |
| 		qint32 version = 0;
 | |
| 		_readOldMtpDataFields(&file, version, context);
 | |
| 		file.close();
 | |
| 		result = true;
 | |
| 	}
 | |
| 	if (remove) file.remove();
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| void _writeUserSettings() {
 | |
| 	if (_readingUserSettings) {
 | |
| 		LOG(("App Error: attempt to write settings while reading them!"));
 | |
| 		return;
 | |
| 	}
 | |
| 	LOG(("App Info: writing encrypted user settings..."));
 | |
| 
 | |
| 	if (!_userSettingsKey) {
 | |
| 		_userSettingsKey = genKey();
 | |
| 		_mapChanged = true;
 | |
| 		_writeMap(WriteMapWhen::Fast);
 | |
| 	}
 | |
| 
 | |
| 	auto recentEmojiPreloadData = cRecentEmojiPreload();
 | |
| 	if (recentEmojiPreloadData.isEmpty()) {
 | |
| 		recentEmojiPreloadData.reserve(GetRecentEmoji().size());
 | |
| 		for (auto &item : GetRecentEmoji()) {
 | |
| 			recentEmojiPreloadData.push_back(qMakePair(item.first->id(), item.second));
 | |
| 		}
 | |
| 	}
 | |
| 	auto userDataInstance = StoredSessionSettings
 | |
| 		? StoredSessionSettings.get()
 | |
| 		: Core::App().activeAccount().getSessionSettings();
 | |
| 	auto userData = userDataInstance
 | |
| 		? userDataInstance->serialize()
 | |
| 		: QByteArray();
 | |
| 	auto callSettings = serializeCallSettings();
 | |
| 
 | |
| 	uint32 size = 23 * (sizeof(quint32) + sizeof(qint32));
 | |
| 	size += sizeof(quint32) + Serialize::stringSize(Global::AskDownloadPath() ? QString() : Global::DownloadPath()) + Serialize::bytearraySize(Global::AskDownloadPath() ? QByteArray() : Global::DownloadPathBookmark());
 | |
| 
 | |
| 	size += sizeof(quint32) + sizeof(qint32);
 | |
| 	for (auto &item : recentEmojiPreloadData) {
 | |
| 		size += Serialize::stringSize(item.first) + sizeof(item.second);
 | |
| 	}
 | |
| 
 | |
| 	size += sizeof(quint32) + sizeof(qint32) + cEmojiVariants().size() * (sizeof(uint32) + sizeof(uint64));
 | |
| 	size += sizeof(quint32) + sizeof(qint32) + (cRecentStickersPreload().isEmpty() ? Stickers::GetRecentPack().size() : cRecentStickersPreload().size()) * (sizeof(uint64) + sizeof(ushort));
 | |
| 	size += sizeof(quint32) + Serialize::stringSize(cDialogLastPath());
 | |
| 	size += sizeof(quint32) + 3 * sizeof(qint32);
 | |
| 	size += sizeof(quint32) + 2 * sizeof(qint32);
 | |
| 	size += sizeof(quint32) + 2 * sizeof(qint32);
 | |
| 	size += sizeof(quint32) + sizeof(qint64) + sizeof(qint32);
 | |
| 	if (!Global::HiddenPinnedMessages().isEmpty()) {
 | |
| 		size += sizeof(quint32) + sizeof(qint32) + Global::HiddenPinnedMessages().size() * (sizeof(PeerId) + sizeof(MsgId));
 | |
| 	}
 | |
| 	if (!userData.isEmpty()) {
 | |
| 		size += sizeof(quint32) + Serialize::bytearraySize(userData);
 | |
| 	}
 | |
| 	size += sizeof(quint32) + Serialize::bytearraySize(callSettings);
 | |
| 
 | |
| 	EncryptedDescriptor data(size);
 | |
| 	data.stream
 | |
| 		<< quint32(dbiTileBackground)
 | |
| 		<< qint32(Window::Theme::Background()->tileDay() ? 1 : 0)
 | |
| 		<< qint32(Window::Theme::Background()->tileNight() ? 1 : 0);
 | |
| 	data.stream << quint32(dbiAdaptiveForWide) << qint32(Global::AdaptiveForWide() ? 1 : 0);
 | |
| 	data.stream << quint32(dbiAutoLock) << qint32(Global::AutoLock());
 | |
| 	data.stream << quint32(dbiSoundNotify) << qint32(Global::SoundNotify());
 | |
| 	data.stream << quint32(dbiDesktopNotify) << qint32(Global::DesktopNotify());
 | |
| 	data.stream << quint32(dbiNotifyView) << qint32(Global::NotifyView());
 | |
| 	data.stream << quint32(dbiNativeNotifications) << qint32(Global::NativeNotifications());
 | |
| 	data.stream << quint32(dbiNotificationsCount) << qint32(Global::NotificationsCount());
 | |
| 	data.stream << quint32(dbiNotificationsCorner) << qint32(Global::NotificationsCorner());
 | |
| 	data.stream << quint32(dbiAskDownloadPath) << qint32(Global::AskDownloadPath());
 | |
| 	data.stream << quint32(dbiDownloadPath) << (Global::AskDownloadPath() ? QString() : Global::DownloadPath()) << (Global::AskDownloadPath() ? QByteArray() : Global::DownloadPathBookmark());
 | |
| 	data.stream << quint32(dbiDialogLastPath) << cDialogLastPath();
 | |
| 	data.stream << quint32(dbiSongVolume) << qint32(qRound(Global::SongVolume() * 1e6));
 | |
| 	data.stream << quint32(dbiVideoVolume) << qint32(qRound(Global::VideoVolume() * 1e6));
 | |
| 	data.stream << quint32(dbiDialogsMode) << qint32(Global::DialogsModeEnabled() ? 1 : 0) << static_cast<qint32>(Global::DialogsMode());
 | |
| 	data.stream << quint32(dbiModerateMode) << qint32(Global::ModerateModeEnabled() ? 1 : 0);
 | |
| 	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(dbiPlaybackSpeed) << qint32(Global::VoiceMsgPlaybackDoubled() ? 2 : 1);
 | |
| 
 | |
| 	{
 | |
| 		data.stream << quint32(dbiRecentEmoji) << recentEmojiPreloadData;
 | |
| 	}
 | |
| 	data.stream << quint32(dbiEmojiVariants) << cEmojiVariants();
 | |
| 	{
 | |
| 		auto v = cRecentStickersPreload();
 | |
| 		if (v.isEmpty()) {
 | |
| 			v.reserve(Stickers::GetRecentPack().size());
 | |
| 			for_const (auto &pair, Stickers::GetRecentPack()) {
 | |
| 				v.push_back(qMakePair(pair.first->id, pair.second));
 | |
| 			}
 | |
| 		}
 | |
| 		data.stream << quint32(dbiRecentStickers) << v;
 | |
| 	}
 | |
| 	if (!Global::HiddenPinnedMessages().isEmpty()) {
 | |
| 		data.stream << quint32(dbiHiddenPinnedMessages) << Global::HiddenPinnedMessages();
 | |
| 	}
 | |
| 	data.stream << qint32(dbiCallSettings) << callSettings;
 | |
| 
 | |
| 	FileWriteDescriptor file(_userSettingsKey);
 | |
| 	file.writeEncrypted(data);
 | |
| }
 | |
| 
 | |
| void _readUserSettings() {
 | |
| 	ReadSettingsContext context;
 | |
| 	FileReadDescriptor userSettings;
 | |
| 	if (!readEncryptedFile(userSettings, _userSettingsKey)) {
 | |
| 		LOG(("App Info: could not read encrypted user settings..."));
 | |
| 
 | |
| 		_readOldUserSettings(true, context);
 | |
| 		applyReadContext(std::move(context));
 | |
| 
 | |
| 		return _writeUserSettings();
 | |
| 	}
 | |
| 
 | |
| 	LOG(("App Info: reading encrypted user settings..."));
 | |
| 	_readingUserSettings = true;
 | |
| 	while (!userSettings.stream.atEnd()) {
 | |
| 		quint32 blockId;
 | |
| 		userSettings.stream >> blockId;
 | |
| 		if (!_checkStreamStatus(userSettings.stream)) {
 | |
| 			_readingUserSettings = false;
 | |
| 			return _writeUserSettings();
 | |
| 		}
 | |
| 
 | |
| 		if (!_readSetting(blockId, userSettings.stream, userSettings.version, context)) {
 | |
| 			_readingUserSettings = false;
 | |
| 			return _writeUserSettings();
 | |
| 		}
 | |
| 	}
 | |
| 	_readingUserSettings = false;
 | |
| 	LOG(("App Info: encrypted user settings read."));
 | |
| 
 | |
| 	applyReadContext(std::move(context));
 | |
| }
 | |
| 
 | |
| void _writeMtpData() {
 | |
| 	FileWriteDescriptor mtp(toFilePart(_dataNameKey), FileOption::Safe);
 | |
| 	if (!LocalKey) {
 | |
| 		LOG(("App Error: localkey not created in _writeMtpData()"));
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	auto mtpAuthorizationSerialized = Core::App().activeAccount().serializeMtpAuthorization();
 | |
| 
 | |
| 	quint32 size = sizeof(quint32) + Serialize::bytearraySize(mtpAuthorizationSerialized);
 | |
| 
 | |
| 	EncryptedDescriptor data(size);
 | |
| 	data.stream << quint32(dbiMtpAuthorization) << mtpAuthorizationSerialized;
 | |
| 	mtp.writeEncrypted(data);
 | |
| }
 | |
| 
 | |
| void _readMtpData() {
 | |
| 	ReadSettingsContext context;
 | |
| 	FileReadDescriptor mtp;
 | |
| 	if (!readEncryptedFile(mtp, toFilePart(_dataNameKey), FileOption::Safe)) {
 | |
| 		if (LocalKey) {
 | |
| 			_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));
 | |
| }
 | |
| 
 | |
| ReadMapState _readMap(const QByteArray &pass) {
 | |
| 	auto ms = crl::now();
 | |
| 	QByteArray dataNameUtf8 = (cDataFile() + (cTestMode() ? qsl(":/test/") : QString())).toUtf8();
 | |
| 	FileKey dataNameHash[2];
 | |
| 	hashMd5(dataNameUtf8.constData(), dataNameUtf8.size(), dataNameHash);
 | |
| 	_dataNameKey = dataNameHash[0];
 | |
| 	_userBasePath = _basePath + toFilePart(_dataNameKey) + QChar('/');
 | |
| 	_userDbPath = _basePath
 | |
| 		+ "user_" + cDataFile()
 | |
| 		+ (cTestMode() ? "[test]" : "")
 | |
| 		+ '/';
 | |
| 
 | |
| 	FileReadDescriptor mapData;
 | |
| 	if (!readFile(mapData, qsl("map"))) {
 | |
| 		return ReadMapFailed;
 | |
| 	}
 | |
| 	LOG(("App Info: reading map..."));
 | |
| 
 | |
| 	QByteArray salt, keyEncrypted, mapEncrypted;
 | |
| 	mapData.stream >> salt >> keyEncrypted >> mapEncrypted;
 | |
| 	if (!_checkStreamStatus(mapData.stream)) {
 | |
| 		return ReadMapFailed;
 | |
| 	}
 | |
| 
 | |
| 	if (salt.size() != LocalEncryptSaltSize) {
 | |
| 		LOG(("App Error: bad salt in map file, size: %1").arg(salt.size()));
 | |
| 		return ReadMapFailed;
 | |
| 	}
 | |
| 	createLocalKey(pass, &salt, &PassKey);
 | |
| 
 | |
| 	EncryptedDescriptor keyData, map;
 | |
| 	if (!decryptLocal(keyData, keyEncrypted, PassKey)) {
 | |
| 		LOG(("App Info: could not decrypt pass-protected key from map file, maybe bad password..."));
 | |
| 		return ReadMapPassNeeded;
 | |
| 	}
 | |
| 	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 ReadMapFailed;
 | |
| 	}
 | |
| 	LocalKey = std::make_shared<MTP::AuthKey>(key);
 | |
| 
 | |
| 	_passKeyEncrypted = keyEncrypted;
 | |
| 	_passKeySalt = salt;
 | |
| 
 | |
| 	if (!decryptLocal(map, mapEncrypted)) {
 | |
| 		LOG(("App Error: could not decrypt map."));
 | |
| 		return ReadMapFailed;
 | |
| 	}
 | |
| 	LOG(("App Info: reading encrypted map..."));
 | |
| 
 | |
| 	QByteArray selfSerialized;
 | |
| 	DraftsMap draftsMap, draftCursorsMap;
 | |
| 	DraftsNotReadMap draftsNotReadMap;
 | |
| 	quint64 locationsKey = 0, reportSpamStatusesKey = 0, trustedBotsKey = 0;
 | |
| 	quint64 recentStickersKeyOld = 0;
 | |
| 	quint64 installedStickersKey = 0, featuredStickersKey = 0, recentStickersKey = 0, favedStickersKey = 0, archivedStickersKey = 0;
 | |
| 	quint64 savedGifsKey = 0;
 | |
| 	quint64 backgroundKeyDay = 0, backgroundKeyNight = 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 p;
 | |
| 				map.stream >> key >> p;
 | |
| 				draftsMap.insert(p, key);
 | |
| 				draftsNotReadMap.insert(p, 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 p;
 | |
| 				map.stream >> key >> p;
 | |
| 				draftCursorsMap.insert(p, 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);
 | |
| 		} break;
 | |
| 		case lskTrustedBots: {
 | |
| 			map.stream >> trustedBotsKey;
 | |
| 		} break;
 | |
| 		case lskRecentStickersOld: {
 | |
| 			map.stream >> recentStickersKeyOld;
 | |
| 		} break;
 | |
| 		case lskBackgroundOld: {
 | |
| 			map.stream >> (Window::Theme::IsNightMode()
 | |
| 				? backgroundKeyNight
 | |
| 				: backgroundKeyDay);
 | |
| 		} break;
 | |
| 		case lskBackground: {
 | |
| 			map.stream >> backgroundKeyDay >> backgroundKeyNight;
 | |
| 		} 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;
 | |
| 		default:
 | |
| 		LOG(("App Error: unknown key type in encrypted map: %1").arg(keyType));
 | |
| 		return ReadMapFailed;
 | |
| 		}
 | |
| 		if (!_checkStreamStatus(map.stream)) {
 | |
| 			return ReadMapFailed;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	_draftsMap = draftsMap;
 | |
| 	_draftCursorsMap = draftCursorsMap;
 | |
| 	_draftsNotReadMap = draftsNotReadMap;
 | |
| 
 | |
| 	_locationsKey = locationsKey;
 | |
| 	_trustedBotsKey = trustedBotsKey;
 | |
| 	_recentStickersKeyOld = recentStickersKeyOld;
 | |
| 	_installedStickersKey = installedStickersKey;
 | |
| 	_featuredStickersKey = featuredStickersKey;
 | |
| 	_recentStickersKey = recentStickersKey;
 | |
| 	_favedStickersKey = favedStickersKey;
 | |
| 	_archivedStickersKey = archivedStickersKey;
 | |
| 	_savedGifsKey = savedGifsKey;
 | |
| 	_backgroundKeyDay = backgroundKeyDay;
 | |
| 	_backgroundKeyNight = backgroundKeyNight;
 | |
| 	_userSettingsKey = userSettingsKey;
 | |
| 	_recentHashtagsAndBotsKey = recentHashtagsAndBotsKey;
 | |
| 	_exportSettingsKey = exportSettingsKey;
 | |
| 	_oldMapVersion = mapData.version;
 | |
| 	if (_oldMapVersion < AppVersion) {
 | |
| 		_mapChanged = true;
 | |
| 		_writeMap();
 | |
| 	} else {
 | |
| 		_mapChanged = false;
 | |
| 	}
 | |
| 
 | |
| 	if (_locationsKey) {
 | |
| 		_readLocations();
 | |
| 	}
 | |
| 
 | |
| 	_readUserSettings();
 | |
| 	_readMtpData();
 | |
| 
 | |
| 	DEBUG_LOG(("selfSerialized set: %1").arg(selfSerialized.size()));
 | |
| 	Core::App().activeAccount().setSessionFromStorage(
 | |
| 		std::move(StoredSessionSettings),
 | |
| 		std::move(selfSerialized),
 | |
| 		_oldMapVersion);
 | |
| 
 | |
| 	LOG(("Map read time: %1").arg(crl::now() - ms));
 | |
| 	if (_oldSettingsVersion < AppVersion) {
 | |
| 		writeSettings();
 | |
| 	}
 | |
| 	return ReadMapDone;
 | |
| }
 | |
| 
 | |
| void _writeMap(WriteMapWhen when) {
 | |
| 	Expects(_manager != nullptr);
 | |
| 
 | |
| 	if (when != WriteMapWhen::Now) {
 | |
| 		_manager->writeMap(when == WriteMapWhen::Fast);
 | |
| 		return;
 | |
| 	}
 | |
| 	_manager->writingMap();
 | |
| 	if (!_mapChanged) return;
 | |
| 	if (_userBasePath.isEmpty()) {
 | |
| 		LOG(("App Error: _userBasePath is empty in writeMap()"));
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (!QDir().exists(_userBasePath)) QDir().mkpath(_userBasePath);
 | |
| 
 | |
| 	FileWriteDescriptor map(qsl("map"));
 | |
| 	if (_passKeySalt.isEmpty() || _passKeyEncrypted.isEmpty()) {
 | |
| 		QByteArray pass(kLocalKeySize, Qt::Uninitialized), salt(LocalEncryptSaltSize, Qt::Uninitialized);
 | |
| 		memset_rand(pass.data(), pass.size());
 | |
| 		memset_rand(salt.data(), salt.size());
 | |
| 		createLocalKey(pass, &salt, &LocalKey);
 | |
| 
 | |
| 		_passKeySalt.resize(LocalEncryptSaltSize);
 | |
| 		memset_rand(_passKeySalt.data(), _passKeySalt.size());
 | |
| 		createLocalKey(QByteArray(), &_passKeySalt, &PassKey);
 | |
| 
 | |
| 		EncryptedDescriptor passKeyData(kLocalKeySize);
 | |
| 		LocalKey->write(passKeyData.stream);
 | |
| 		_passKeyEncrypted = FileWriteDescriptor::prepareEncrypted(passKeyData, PassKey);
 | |
| 	}
 | |
| 	map.writeData(_passKeySalt);
 | |
| 	map.writeData(_passKeyEncrypted);
 | |
| 
 | |
| 	uint32 mapSize = 0;
 | |
| 	const auto self = [] {
 | |
| 		if (!Main::Session::Exists()) {
 | |
| 			DEBUG_LOG(("AuthSelf Warning: Session does not exist."));
 | |
| 			return QByteArray();
 | |
| 		}
 | |
| 		const auto self = Auth().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.isEmpty()) mapSize += sizeof(quint32) * 2 + _draftsMap.size() * sizeof(quint64) * 2;
 | |
| 	if (!_draftCursorsMap.isEmpty()) 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 (_backgroundKeyDay || _backgroundKeyNight) mapSize += sizeof(quint32) + sizeof(quint64) + sizeof(quint64);
 | |
| 	if (_userSettingsKey) mapSize += sizeof(quint32) + sizeof(quint64);
 | |
| 	if (_recentHashtagsAndBotsKey) mapSize += sizeof(quint32) + sizeof(quint64);
 | |
| 	if (_exportSettingsKey) mapSize += sizeof(quint32) + sizeof(quint64);
 | |
| 
 | |
| 	EncryptedDescriptor mapData(mapSize);
 | |
| 	if (!self.isEmpty()) {
 | |
| 		mapData.stream << quint32(lskSelfSerialized) << self;
 | |
| 	}
 | |
| 	if (!_draftsMap.isEmpty()) {
 | |
| 		mapData.stream << quint32(lskDraft) << quint32(_draftsMap.size());
 | |
| 		for (DraftsMap::const_iterator i = _draftsMap.cbegin(), e = _draftsMap.cend(); i != e; ++i) {
 | |
| 			mapData.stream << quint64(i.value()) << quint64(i.key());
 | |
| 		}
 | |
| 	}
 | |
| 	if (!_draftCursorsMap.isEmpty()) {
 | |
| 		mapData.stream << quint32(lskDraftPosition) << quint32(_draftCursorsMap.size());
 | |
| 		for (DraftsMap::const_iterator i = _draftCursorsMap.cbegin(), e = _draftCursorsMap.cend(); i != e; ++i) {
 | |
| 			mapData.stream << quint64(i.value()) << quint64(i.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 (_backgroundKeyDay || _backgroundKeyNight) {
 | |
| 		mapData.stream
 | |
| 			<< quint32(lskBackground)
 | |
| 			<< quint64(_backgroundKeyDay)
 | |
| 			<< quint64(_backgroundKeyNight);
 | |
| 	}
 | |
| 	if (_userSettingsKey) {
 | |
| 		mapData.stream << quint32(lskUserSettings) << quint64(_userSettingsKey);
 | |
| 	}
 | |
| 	if (_recentHashtagsAndBotsKey) {
 | |
| 		mapData.stream << quint32(lskRecentHashtagsAndBots) << quint64(_recentHashtagsAndBotsKey);
 | |
| 	}
 | |
| 	if (_exportSettingsKey) {
 | |
| 		mapData.stream << quint32(lskExportSettings) << quint64(_exportSettingsKey);
 | |
| 	}
 | |
| 	map.writeEncrypted(mapData);
 | |
| 
 | |
| 	_mapChanged = false;
 | |
| }
 | |
| 
 | |
| } // namespace
 | |
| 
 | |
| void finish() {
 | |
| 	if (_manager) {
 | |
| 		_writeMap(WriteMapWhen::Now);
 | |
| 		_manager->finish();
 | |
| 		_manager->deleteLater();
 | |
| 		_manager = nullptr;
 | |
| 		delete base::take(_localLoader);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void InitialLoadTheme();
 | |
| void readLangPack();
 | |
| 
 | |
| void start() {
 | |
| 	Expects(!_manager);
 | |
| 
 | |
| 	_manager = new internal::Manager();
 | |
| 	_localLoader = new TaskQueue(kFileLoaderQueueStopTimeout);
 | |
| 
 | |
| 	_basePath = cWorkingDir() + qsl("tdata/");
 | |
| 	if (!QDir().exists(_basePath)) QDir().mkpath(_basePath);
 | |
| 
 | |
| 	ReadSettingsContext context;
 | |
| 	FileReadDescriptor settingsData;
 | |
| 	if (!readFile(settingsData, cTestMode() ? qsl("settings_test") : qsl("settings"), FileOption::Safe)) {
 | |
| 		_readOldSettings(true, context);
 | |
| 		_readOldUserSettings(false, context); // needed further in _readUserSettings
 | |
| 		_readOldMtpData(false, context); // needed further in _readMtpData
 | |
| 		applyReadContext(std::move(context));
 | |
| 
 | |
| 		return writeSettings();
 | |
| 	}
 | |
| 	LOG(("App Info: reading settings..."));
 | |
| 
 | |
| 	QByteArray salt, settingsEncrypted;
 | |
| 	settingsData.stream >> salt >> settingsEncrypted;
 | |
| 	if (!_checkStreamStatus(settingsData.stream)) {
 | |
| 		return writeSettings();
 | |
| 	}
 | |
| 
 | |
| 	if (salt.size() != LocalEncryptSaltSize) {
 | |
| 		LOG(("App Error: bad salt in settings file, size: %1").arg(salt.size()));
 | |
| 		return writeSettings();
 | |
| 	}
 | |
| 	createLocalKey(QByteArray(), &salt, &SettingsKey);
 | |
| 
 | |
| 	EncryptedDescriptor settings;
 | |
| 	if (!decryptLocal(settings, settingsEncrypted, SettingsKey)) {
 | |
| 		LOG(("App Error: could not decrypt settings from settings file, maybe bad passcode..."));
 | |
| 		return writeSettings();
 | |
| 	}
 | |
| 
 | |
| 	LOG(("App Info: reading encrypted settings..."));
 | |
| 	while (!settings.stream.atEnd()) {
 | |
| 		quint32 blockId;
 | |
| 		settings.stream >> blockId;
 | |
| 		if (!_checkStreamStatus(settings.stream)) {
 | |
| 			return writeSettings();
 | |
| 		}
 | |
| 
 | |
| 		if (!_readSetting(blockId, settings.stream, settingsData.version, context)) {
 | |
| 			return writeSettings();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	_oldSettingsVersion = settingsData.version;
 | |
| 	_settingsSalt = salt;
 | |
| 
 | |
| 	InitialLoadTheme();
 | |
| 	readLangPack();
 | |
| 
 | |
| 	applyReadContext(std::move(context));
 | |
| }
 | |
| 
 | |
| void writeSettings() {
 | |
| 	if (_basePath.isEmpty()) {
 | |
| 		LOG(("App Error: _basePath is empty in writeSettings()"));
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (!QDir().exists(_basePath)) QDir().mkpath(_basePath);
 | |
| 
 | |
| 	FileWriteDescriptor settings(cTestMode() ? qsl("settings_test") : qsl("settings"), FileOption::Safe);
 | |
| 	if (_settingsSalt.isEmpty() || !SettingsKey) {
 | |
| 		_settingsSalt.resize(LocalEncryptSaltSize);
 | |
| 		memset_rand(_settingsSalt.data(), _settingsSalt.size());
 | |
| 		createLocalKey(QByteArray(), &_settingsSalt, &SettingsKey);
 | |
| 	}
 | |
| 	settings.writeData(_settingsSalt);
 | |
| 
 | |
| 	const auto dcOptionsSerialized = Core::App().dcOptions()->serialize();
 | |
| 	const auto applicationSettings = Core::App().settings().serialize();
 | |
| 
 | |
| 	quint32 size = 12 * (sizeof(quint32) + sizeof(qint32));
 | |
| 	size += sizeof(quint32) + Serialize::bytearraySize(dcOptionsSerialized);
 | |
| 	size += sizeof(quint32) + Serialize::bytearraySize(applicationSettings);
 | |
| 	size += sizeof(quint32) + Serialize::stringSize(cLoggedPhoneNumber());
 | |
| 	size += sizeof(quint32) + Serialize::stringSize(Global::TxtDomainString());
 | |
| 
 | |
| 	auto &proxies = Global::RefProxiesList();
 | |
| 	const auto &proxy = Global::SelectedProxy();
 | |
| 	auto proxyIt = ranges::find(proxies, proxy);
 | |
| 	if (proxy.type != MTP::ProxyData::Type::None
 | |
| 		&& proxyIt == end(proxies)) {
 | |
| 		proxies.push_back(proxy);
 | |
| 		proxyIt = end(proxies) - 1;
 | |
| 	}
 | |
| 	size += sizeof(quint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32);
 | |
| 	for (const auto &proxy : proxies) {
 | |
| 		size += sizeof(qint32) + Serialize::stringSize(proxy.host) + sizeof(qint32) + Serialize::stringSize(proxy.user) + Serialize::stringSize(proxy.password);
 | |
| 	}
 | |
| 
 | |
| 	// Theme keys and night mode.
 | |
| 	size += sizeof(quint32) + sizeof(quint64) * 2 + sizeof(quint32);
 | |
| 	if (_langPackKey) {
 | |
| 		size += sizeof(quint32) + sizeof(quint64);
 | |
| 	}
 | |
| 	size += sizeof(quint32) + sizeof(qint32) * 8;
 | |
| 
 | |
| 	EncryptedDescriptor data(size);
 | |
| 	data.stream << quint32(dbiChatSizeMax) << qint32(Global::ChatSizeMax());
 | |
| 	data.stream << quint32(dbiMegagroupSizeMax) << qint32(Global::MegagroupSizeMax());
 | |
| 	data.stream << quint32(dbiSavedGifsLimit) << qint32(Global::SavedGifsLimit());
 | |
| 	data.stream << quint32(dbiStickersRecentLimit) << qint32(Global::StickersRecentLimit());
 | |
| 	data.stream << quint32(dbiStickersFavedLimit) << qint32(Global::StickersFavedLimit());
 | |
| 	data.stream << quint32(dbiAutoStart) << qint32(cAutoStart());
 | |
| 	data.stream << quint32(dbiStartMinimized) << qint32(cStartMinimized());
 | |
| 	data.stream << quint32(dbiSendToMenu) << qint32(cSendToMenu());
 | |
| 	data.stream << quint32(dbiWorkMode) << qint32(Global::WorkMode().value());
 | |
| 	data.stream << quint32(dbiSeenTrayTooltip) << qint32(cSeenTrayTooltip());
 | |
| 	data.stream << quint32(dbiAutoUpdate) << qint32(cAutoUpdate());
 | |
| 	data.stream << quint32(dbiLastUpdateCheck) << qint32(cLastUpdateCheck());
 | |
| 	data.stream << quint32(dbiScalePercent) << qint32(cConfigScale());
 | |
| 	data.stream << quint32(dbiDcOptions) << dcOptionsSerialized;
 | |
| 	data.stream << quint32(dbiApplicationSettings) << applicationSettings;
 | |
| 	data.stream << quint32(dbiLoggedPhoneNumber) << cLoggedPhoneNumber();
 | |
| 	data.stream << quint32(dbiTxtDomainString) << Global::TxtDomainString();
 | |
| 	data.stream << quint32(dbiAnimationsDisabled) << qint32(anim::Disabled() ? 1 : 0);
 | |
| 
 | |
| 	data.stream << quint32(dbiConnectionType) << qint32(dbictProxiesList);
 | |
| 	data.stream << qint32(proxies.size());
 | |
| 	data.stream << qint32(proxyIt - begin(proxies)) + 1;
 | |
| 	data.stream << qint32(Global::ProxySettings());
 | |
| 	data.stream << qint32(Global::UseProxyForCalls() ? 1 : 0);
 | |
| 	for (const auto &proxy : proxies) {
 | |
| 		data.stream << qint32(kProxyTypeShift + int(proxy.type));
 | |
| 		data.stream << proxy.host << qint32(proxy.port) << proxy.user << proxy.password;
 | |
| 	}
 | |
| 
 | |
| 	data.stream << quint32(dbiTryIPv6) << qint32(Global::TryIPv6());
 | |
| 	data.stream
 | |
| 		<< quint32(dbiThemeKey)
 | |
| 		<< quint64(_themeKeyDay)
 | |
| 		<< quint64(_themeKeyNight)
 | |
| 		<< quint32(Window::Theme::IsNightMode() ? 1 : 0);
 | |
| 	if (_langPackKey) {
 | |
| 		data.stream << quint32(dbiLangPackKey) << quint64(_langPackKey);
 | |
| 	}
 | |
| 	if (_languagesKey) {
 | |
| 		data.stream << quint32(dbiLanguagesKey) << quint64(_languagesKey);
 | |
| 	}
 | |
| 
 | |
| 	auto position = cWindowPos();
 | |
| 	data.stream << quint32(dbiWindowPosition) << qint32(position.x) << qint32(position.y) << qint32(position.w) << qint32(position.h);
 | |
| 	data.stream << qint32(position.moncrc) << qint32(position.maximized);
 | |
| 
 | |
| 	DEBUG_LOG(("Window Pos: Writing to storage %1, %2, %3, %4 (maximized %5)").arg(position.x).arg(position.y).arg(position.w).arg(position.h).arg(Logs::b(position.maximized)));
 | |
| 
 | |
| 	settings.writeEncrypted(data, SettingsKey);
 | |
| }
 | |
| 
 | |
| void writeUserSettings() {
 | |
| 	_writeUserSettings();
 | |
| }
 | |
| 
 | |
| void writeMtpData() {
 | |
| 	_writeMtpData();
 | |
| }
 | |
| 
 | |
| const QString &AutoupdatePrefix(const QString &replaceWith = {}) {
 | |
| 	Expects(!Core::UpdaterDisabled());
 | |
| 
 | |
| 	static auto value = QString();
 | |
| 	if (!replaceWith.isEmpty()) {
 | |
| 		value = replaceWith;
 | |
| 	}
 | |
| 	return value;
 | |
| }
 | |
| 
 | |
| QString autoupdatePrefixFile() {
 | |
| 	Expects(!Core::UpdaterDisabled());
 | |
| 
 | |
| 	return cWorkingDir() + "tdata/prefix";
 | |
| }
 | |
| 
 | |
| const QString &readAutoupdatePrefixRaw() {
 | |
| 	Expects(!Core::UpdaterDisabled());
 | |
| 
 | |
| 	const auto &result = AutoupdatePrefix();
 | |
| 	if (!result.isEmpty()) {
 | |
| 		return result;
 | |
| 	}
 | |
| 	QFile f(autoupdatePrefixFile());
 | |
| 	if (f.open(QIODevice::ReadOnly)) {
 | |
| 		const auto value = QString::fromUtf8(f.readAll());
 | |
| 		if (!value.isEmpty()) {
 | |
| 			return AutoupdatePrefix(value);
 | |
| 		}
 | |
| 	}
 | |
| 	return AutoupdatePrefix("https://updates.tdesktop.com");
 | |
| }
 | |
| 
 | |
| void writeAutoupdatePrefix(const QString &prefix) {
 | |
| 	if (Core::UpdaterDisabled()) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	const auto current = readAutoupdatePrefixRaw();
 | |
| 	if (current != prefix) {
 | |
| 		AutoupdatePrefix(prefix);
 | |
| 		QFile f(autoupdatePrefixFile());
 | |
| 		if (f.open(QIODevice::WriteOnly)) {
 | |
| 			f.write(prefix.toUtf8());
 | |
| 			f.close();
 | |
| 		}
 | |
| 		if (cAutoUpdate()) {
 | |
| 			Core::UpdateChecker checker;
 | |
| 			checker.start();
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| QString readAutoupdatePrefix() {
 | |
| 	Expects(!Core::UpdaterDisabled());
 | |
| 
 | |
| 	auto result = readAutoupdatePrefixRaw();
 | |
| 	return result.replace(QRegularExpression("/+$"), QString());
 | |
| }
 | |
| 
 | |
| void reset() {
 | |
| 	if (_localLoader) {
 | |
| 		_localLoader->stop();
 | |
| 	}
 | |
| 
 | |
| 	_passKeySalt.clear(); // reset passcode, local key
 | |
| 	_draftsMap.clear();
 | |
| 	_draftCursorsMap.clear();
 | |
| 	_fileLocations.clear();
 | |
| 	_fileLocationPairs.clear();
 | |
| 	_fileLocationAliases.clear();
 | |
| 	_draftsNotReadMap.clear();
 | |
| 	_locationsKey = _trustedBotsKey = 0;
 | |
| 	_recentStickersKeyOld = 0;
 | |
| 	_installedStickersKey = _featuredStickersKey = _recentStickersKey = _favedStickersKey = _archivedStickersKey = 0;
 | |
| 	_savedGifsKey = 0;
 | |
| 	_backgroundKeyDay = _backgroundKeyNight = 0;
 | |
| 	Window::Theme::Background()->reset();
 | |
| 	_userSettingsKey = _recentHashtagsAndBotsKey = _exportSettingsKey = 0;
 | |
| 	_oldMapVersion = _oldSettingsVersion = 0;
 | |
| 	_cacheTotalSizeLimit = Database::Settings().totalSizeLimit;
 | |
| 	_cacheTotalTimeLimit = Database::Settings().totalTimeLimit;
 | |
| 	_cacheBigFileTotalSizeLimit = Database::Settings().totalSizeLimit;
 | |
| 	_cacheBigFileTotalTimeLimit = Database::Settings().totalTimeLimit;
 | |
| 	StoredSessionSettings.reset();
 | |
| 	_mapChanged = true;
 | |
| 	_writeMap(WriteMapWhen::Now);
 | |
| 
 | |
| 	_writeMtpData();
 | |
| }
 | |
| 
 | |
| bool checkPasscode(const QByteArray &passcode) {
 | |
| 	auto checkKey = MTP::AuthKeyPtr();
 | |
| 	createLocalKey(passcode, &_passKeySalt, &checkKey);
 | |
| 	return checkKey->equals(PassKey);
 | |
| }
 | |
| 
 | |
| void setPasscode(const QByteArray &passcode) {
 | |
| 	createLocalKey(passcode, &_passKeySalt, &PassKey);
 | |
| 
 | |
| 	EncryptedDescriptor passKeyData(kLocalKeySize);
 | |
| 	LocalKey->write(passKeyData.stream);
 | |
| 	_passKeyEncrypted = FileWriteDescriptor::prepareEncrypted(passKeyData, PassKey);
 | |
| 
 | |
| 	_mapChanged = true;
 | |
| 	_writeMap(WriteMapWhen::Now);
 | |
| 
 | |
| 	Global::SetLocalPasscode(!passcode.isEmpty());
 | |
| 	Global::RefLocalPasscodeChanged().notify();
 | |
| }
 | |
| 
 | |
| base::flat_set<QString> CollectGoodNames() {
 | |
| 	const auto keys = {
 | |
| 		_locationsKey,
 | |
| 		_userSettingsKey,
 | |
| 		_installedStickersKey,
 | |
| 		_featuredStickersKey,
 | |
| 		_recentStickersKey,
 | |
| 		_favedStickersKey,
 | |
| 		_archivedStickersKey,
 | |
| 		_recentStickersKeyOld,
 | |
| 		_savedGifsKey,
 | |
| 		_backgroundKeyNight,
 | |
| 		_backgroundKeyDay,
 | |
| 		_recentHashtagsAndBotsKey,
 | |
| 		_exportSettingsKey,
 | |
| 		_trustedBotsKey
 | |
| 	};
 | |
| 	auto result = base::flat_set<QString>{ "map0", "map1" };
 | |
| 	const auto push = [&](FileKey key) {
 | |
| 		if (!key) {
 | |
| 			return;
 | |
| 		}
 | |
| 		auto name = toFilePart(key) + '0';
 | |
| 		result.emplace(name);
 | |
| 		name[name.size() - 1] = '1';
 | |
| 		result.emplace(name);
 | |
| 	};
 | |
| 	for (const auto &value : _draftsMap) {
 | |
| 		push(value);
 | |
| 	}
 | |
| 	for (const auto &value : _draftCursorsMap) {
 | |
| 		push(value);
 | |
| 	}
 | |
| 	for (const auto &value : keys) {
 | |
| 		push(value);
 | |
| 	}
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| void FilterLegacyFiles(FnMut<void(base::flat_set<QString>&&)> then) {
 | |
| 	crl::on_main([then = std::move(then)]() mutable {
 | |
| 		then(CollectGoodNames());
 | |
| 	});
 | |
| }
 | |
| 
 | |
| ReadMapState readMap(const QByteArray &pass) {
 | |
| 	ReadMapState result = _readMap(pass);
 | |
| 	if (result == ReadMapFailed) {
 | |
| 		_mapChanged = true;
 | |
| 		_writeMap(WriteMapWhen::Now);
 | |
| 	}
 | |
| 	if (result != ReadMapPassNeeded) {
 | |
| 		Storage::ClearLegacyFiles(_userBasePath, FilterLegacyFiles);
 | |
| 	}
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| int32 oldMapVersion() {
 | |
| 	return _oldMapVersion;
 | |
| }
 | |
| 
 | |
| int32 oldSettingsVersion() {
 | |
| 	return _oldSettingsVersion;
 | |
| }
 | |
| 
 | |
| void writeDrafts(const PeerId &peer, const MessageDraft &localDraft, const MessageDraft &editDraft) {
 | |
| 	if (!_working()) return;
 | |
| 
 | |
| 	if (localDraft.msgId <= 0 && localDraft.textWithTags.text.isEmpty() && editDraft.msgId <= 0) {
 | |
| 		auto i = _draftsMap.find(peer);
 | |
| 		if (i != _draftsMap.cend()) {
 | |
| 			clearKey(i.value());
 | |
| 			_draftsMap.erase(i);
 | |
| 			_mapChanged = true;
 | |
| 			_writeMap();
 | |
| 		}
 | |
| 
 | |
| 		_draftsNotReadMap.remove(peer);
 | |
| 	} else {
 | |
| 		auto i = _draftsMap.constFind(peer);
 | |
| 		if (i == _draftsMap.cend()) {
 | |
| 			i = _draftsMap.insert(peer, genKey());
 | |
| 			_mapChanged = true;
 | |
| 			_writeMap(WriteMapWhen::Fast);
 | |
| 		}
 | |
| 
 | |
| 		auto msgTags = TextUtilities::SerializeTags(
 | |
| 			localDraft.textWithTags.tags);
 | |
| 		auto editTags = TextUtilities::SerializeTags(
 | |
| 			editDraft.textWithTags.tags);
 | |
| 
 | |
| 		int size = sizeof(quint64);
 | |
| 		size += Serialize::stringSize(localDraft.textWithTags.text) + Serialize::bytearraySize(msgTags) + 2 * sizeof(qint32);
 | |
| 		size += Serialize::stringSize(editDraft.textWithTags.text) + Serialize::bytearraySize(editTags) + 2 * sizeof(qint32);
 | |
| 
 | |
| 		EncryptedDescriptor data(size);
 | |
| 		data.stream << quint64(peer);
 | |
| 		data.stream << localDraft.textWithTags.text << msgTags;
 | |
| 		data.stream << qint32(localDraft.msgId) << qint32(localDraft.previewCancelled ? 1 : 0);
 | |
| 		data.stream << editDraft.textWithTags.text << editTags;
 | |
| 		data.stream << qint32(editDraft.msgId) << qint32(editDraft.previewCancelled ? 1 : 0);
 | |
| 
 | |
| 		FileWriteDescriptor file(i.value());
 | |
| 		file.writeEncrypted(data);
 | |
| 
 | |
| 		_draftsNotReadMap.remove(peer);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void clearDraftCursors(const PeerId &peer) {
 | |
| 	DraftsMap::iterator i = _draftCursorsMap.find(peer);
 | |
| 	if (i != _draftCursorsMap.cend()) {
 | |
| 		clearKey(i.value());
 | |
| 		_draftCursorsMap.erase(i);
 | |
| 		_mapChanged = true;
 | |
| 		_writeMap();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void _readDraftCursors(const PeerId &peer, MessageCursor &localCursor, MessageCursor &editCursor) {
 | |
| 	DraftsMap::iterator j = _draftCursorsMap.find(peer);
 | |
| 	if (j == _draftCursorsMap.cend()) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	FileReadDescriptor draft;
 | |
| 	if (!readEncryptedFile(draft, j.value())) {
 | |
| 		clearDraftCursors(peer);
 | |
| 		return;
 | |
| 	}
 | |
| 	quint64 draftPeer;
 | |
| 	qint32 localPosition = 0, localAnchor = 0, localScroll = QFIXED_MAX;
 | |
| 	qint32 editPosition = 0, editAnchor = 0, editScroll = QFIXED_MAX;
 | |
| 	draft.stream >> draftPeer >> localPosition >> localAnchor >> localScroll;
 | |
| 	if (!draft.stream.atEnd()) {
 | |
| 		draft.stream >> editPosition >> editAnchor >> editScroll;
 | |
| 	}
 | |
| 
 | |
| 	if (draftPeer != peer) {
 | |
| 		clearDraftCursors(peer);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	localCursor = MessageCursor(localPosition, localAnchor, localScroll);
 | |
| 	editCursor = MessageCursor(editPosition, editAnchor, editScroll);
 | |
| }
 | |
| 
 | |
| void readDraftsWithCursors(History *h) {
 | |
| 	PeerId peer = h->peer->id;
 | |
| 	if (!_draftsNotReadMap.remove(peer)) {
 | |
| 		clearDraftCursors(peer);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	DraftsMap::iterator j = _draftsMap.find(peer);
 | |
| 	if (j == _draftsMap.cend()) {
 | |
| 		clearDraftCursors(peer);
 | |
| 		return;
 | |
| 	}
 | |
| 	FileReadDescriptor draft;
 | |
| 	if (!readEncryptedFile(draft, j.value())) {
 | |
| 		clearKey(j.value());
 | |
| 		_draftsMap.erase(j);
 | |
| 		clearDraftCursors(peer);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	quint64 draftPeer = 0;
 | |
| 	TextWithTags msgData, editData;
 | |
| 	QByteArray msgTagsSerialized, editTagsSerialized;
 | |
| 	qint32 msgReplyTo = 0, msgPreviewCancelled = 0, editMsgId = 0, editPreviewCancelled = 0;
 | |
| 	draft.stream >> draftPeer >> 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;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if (draftPeer != peer) {
 | |
| 		clearKey(j.value());
 | |
| 		_draftsMap.erase(j);
 | |
| 		clearDraftCursors(peer);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	msgData.tags = TextUtilities::DeserializeTags(
 | |
| 		msgTagsSerialized,
 | |
| 		msgData.text.size());
 | |
| 	editData.tags = TextUtilities::DeserializeTags(
 | |
| 		editTagsSerialized,
 | |
| 		editData.text.size());
 | |
| 
 | |
| 	MessageCursor msgCursor, editCursor;
 | |
| 	_readDraftCursors(peer, msgCursor, editCursor);
 | |
| 
 | |
| 	if (!h->localDraft()) {
 | |
| 		if (msgData.text.isEmpty() && !msgReplyTo) {
 | |
| 			h->clearLocalDraft();
 | |
| 		} else {
 | |
| 			h->setLocalDraft(std::make_unique<Data::Draft>(
 | |
| 				msgData,
 | |
| 				msgReplyTo,
 | |
| 				msgCursor,
 | |
| 				msgPreviewCancelled));
 | |
| 		}
 | |
| 	}
 | |
| 	if (!editMsgId) {
 | |
| 		h->clearEditDraft();
 | |
| 	} else {
 | |
| 		h->setEditDraft(std::make_unique<Data::Draft>(
 | |
| 			editData,
 | |
| 			editMsgId,
 | |
| 			editCursor,
 | |
| 			editPreviewCancelled));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void writeDraftCursors(const PeerId &peer, const MessageCursor &msgCursor, const MessageCursor &editCursor) {
 | |
| 	if (!_working()) return;
 | |
| 
 | |
| 	if (msgCursor == MessageCursor() && editCursor == MessageCursor()) {
 | |
| 		clearDraftCursors(peer);
 | |
| 	} else {
 | |
| 		DraftsMap::const_iterator i = _draftCursorsMap.constFind(peer);
 | |
| 		if (i == _draftCursorsMap.cend()) {
 | |
| 			i = _draftCursorsMap.insert(peer, genKey());
 | |
| 			_mapChanged = true;
 | |
| 			_writeMap(WriteMapWhen::Fast);
 | |
| 		}
 | |
| 
 | |
| 		EncryptedDescriptor data(sizeof(quint64) + sizeof(qint32) * 3);
 | |
| 		data.stream << quint64(peer) << qint32(msgCursor.position) << qint32(msgCursor.anchor) << qint32(msgCursor.scroll);
 | |
| 		data.stream << qint32(editCursor.position) << qint32(editCursor.anchor) << qint32(editCursor.scroll);
 | |
| 
 | |
| 		FileWriteDescriptor file(i.value());
 | |
| 		file.writeEncrypted(data);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool hasDraftCursors(const PeerId &peer) {
 | |
| 	return _draftCursorsMap.contains(peer);
 | |
| }
 | |
| 
 | |
| bool hasDraft(const PeerId &peer) {
 | |
| 	return _draftsMap.contains(peer);
 | |
| }
 | |
| 
 | |
| void writeFileLocation(MediaKey location, const FileLocation &local) {
 | |
| 	if (local.fname.isEmpty()) {
 | |
| 		return;
 | |
| 	}
 | |
| 	if (!local.inMediaCache()) {
 | |
| 		FileLocationAliases::const_iterator aliasIt = _fileLocationAliases.constFind(location);
 | |
| 		if (aliasIt != _fileLocationAliases.cend()) {
 | |
| 			location = aliasIt.value();
 | |
| 		}
 | |
| 
 | |
| 		FileLocationPairs::iterator 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);
 | |
| 					_writeLocations(WriteMapWhen::Fast);
 | |
| 				}
 | |
| 				return;
 | |
| 			}
 | |
| 			if (i.value().first != location) {
 | |
| 				for (FileLocations::iterator j = _fileLocations.find(i.value().first), e = _fileLocations.end(); (j != e) && (j.key() == i.value().first);) {
 | |
| 					if (j.value() == i.value().second) {
 | |
| 						_fileLocations.erase(j);
 | |
| 						break;
 | |
| 					}
 | |
| 				}
 | |
| 				_fileLocationPairs.erase(i);
 | |
| 			}
 | |
| 		}
 | |
| 		_fileLocationPairs.insert(local.fname, FileLocationPair(location, local));
 | |
| 	} else {
 | |
| 		for (FileLocations::iterator 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);
 | |
| 	_writeLocations(WriteMapWhen::Fast);
 | |
| }
 | |
| 
 | |
| void removeFileLocation(MediaKey location) {
 | |
| 	FileLocations::iterator i = _fileLocations.find(location);
 | |
| 	if (i == _fileLocations.end()) {
 | |
| 		return;
 | |
| 	}
 | |
| 	while (i != _fileLocations.end() && (i.key() == location)) {
 | |
| 		i = _fileLocations.erase(i);
 | |
| 	}
 | |
| 	_writeLocations(WriteMapWhen::Fast);
 | |
| }
 | |
| 
 | |
| FileLocation readFileLocation(MediaKey location) {
 | |
| 	FileLocationAliases::const_iterator aliasIt = _fileLocationAliases.constFind(location);
 | |
| 	if (aliasIt != _fileLocationAliases.cend()) {
 | |
| 		location = aliasIt.value();
 | |
| 	}
 | |
| 
 | |
| 	for (FileLocations::iterator 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);
 | |
| 			_writeLocations();
 | |
| 			continue;
 | |
| 		}
 | |
| 		return i.value();
 | |
| 	}
 | |
| 	return FileLocation();
 | |
| }
 | |
| 
 | |
| Storage::EncryptionKey cacheKey() {
 | |
| 	Expects(LocalKey != nullptr);
 | |
| 
 | |
| 	return Storage::EncryptionKey(bytes::make_vector(LocalKey->data()));
 | |
| }
 | |
| 
 | |
| Storage::EncryptionKey cacheBigFileKey() {
 | |
| 	return cacheKey();
 | |
| }
 | |
| 
 | |
| QString cachePath() {
 | |
| 	Expects(!_userDbPath.isEmpty());
 | |
| 
 | |
| 	return _userDbPath + "cache";
 | |
| }
 | |
| 
 | |
| Storage::Cache::Database::Settings cacheSettings() {
 | |
| 	auto result = Storage::Cache::Database::Settings();
 | |
| 	result.clearOnWrongKey = true;
 | |
| 	result.totalSizeLimit = _cacheTotalSizeLimit;
 | |
| 	result.totalTimeLimit = _cacheTotalTimeLimit;
 | |
| 	result.maxDataSize = Storage::kMaxFileInMemory;
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| void updateCacheSettings(
 | |
| 		Storage::Cache::Database::SettingsUpdate &update,
 | |
| 		Storage::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;
 | |
| 	_writeUserSettings();
 | |
| }
 | |
| 
 | |
| QString cacheBigFilePath() {
 | |
| 	Expects(!_userDbPath.isEmpty());
 | |
| 
 | |
| 	return _userDbPath + "media_cache";
 | |
| }
 | |
| 
 | |
| Storage::Cache::Database::Settings cacheBigFileSettings() {
 | |
| 	auto result = Storage::Cache::Database::Settings();
 | |
| 	result.clearOnWrongKey = true;
 | |
| 	result.totalSizeLimit = _cacheBigFileTotalSizeLimit;
 | |
| 	result.totalTimeLimit = _cacheBigFileTotalTimeLimit;
 | |
| 	result.maxDataSize = Storage::kMaxFileInMemory;
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| class CountWaveformTask : public Task {
 | |
| public:
 | |
| 	CountWaveformTask(DocumentData *doc)
 | |
| 		: _doc(doc)
 | |
| 		, _loc(doc->location(true))
 | |
| 		, _data(doc->data())
 | |
| 		, _wavemax(0) {
 | |
| 		if (_data.isEmpty() && !_loc.accessEnable()) {
 | |
| 			_doc = nullptr;
 | |
| 		}
 | |
| 	}
 | |
| 	void process() override {
 | |
| 		if (!_doc) return;
 | |
| 
 | |
| 		_waveform = audioCountWaveform(_loc, _data);
 | |
| 		_wavemax = _waveform.empty()
 | |
| 			? char(0)
 | |
| 			: *ranges::max_element(_waveform);
 | |
| 	}
 | |
| 	void finish() override {
 | |
| 		if (const auto voice = _doc ? _doc->voice() : nullptr) {
 | |
| 			if (!_waveform.isEmpty()) {
 | |
| 				voice->waveform = _waveform;
 | |
| 				voice->wavemax = _wavemax;
 | |
| 			}
 | |
| 			if (voice->waveform.isEmpty()) {
 | |
| 				voice->waveform.resize(1);
 | |
| 				voice->waveform[0] = -2;
 | |
| 				voice->wavemax = 0;
 | |
| 			} else if (voice->waveform[0] < 0) {
 | |
| 				voice->waveform[0] = -2;
 | |
| 				voice->wavemax = 0;
 | |
| 			}
 | |
| 			Auth().data().requestDocumentViewRepaint(_doc);
 | |
| 		}
 | |
| 	}
 | |
| 	~CountWaveformTask() {
 | |
| 		if (_data.isEmpty() && _doc) {
 | |
| 			_loc.accessDisable();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| protected:
 | |
| 	DocumentData *_doc;
 | |
| 	FileLocation _loc;
 | |
| 	QByteArray _data;
 | |
| 	VoiceWaveform _waveform;
 | |
| 	char _wavemax;
 | |
| 
 | |
| };
 | |
| 
 | |
| void countVoiceWaveform(DocumentData *document) {
 | |
| 	if (const auto voice = document->voice()) {
 | |
| 		if (_localLoader) {
 | |
| 			voice->waveform.resize(1 + sizeof(TaskId));
 | |
| 			voice->waveform[0] = -1; // counting
 | |
| 			TaskId taskId = _localLoader->addTask(
 | |
| 				std::make_unique<CountWaveformTask>(document));
 | |
| 			memcpy(voice->waveform.data() + 1, &taskId, sizeof(taskId));
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void cancelTask(TaskId id) {
 | |
| 	if (_localLoader) {
 | |
| 		_localLoader->cancelTask(id);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void _writeStickerSet(QDataStream &stream, const Stickers::Set &set) {
 | |
| 	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::writeStorageImageLocation(
 | |
| 			stream,
 | |
| 			set.thumbnail ? set.thumbnail->location() : StorageImageLocation());
 | |
| 	};
 | |
| 	if (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded) {
 | |
| 		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 Stickers::Set, which returns a StickerSetCheckResult.
 | |
| template <typename CheckSet>
 | |
| void _writeStickerSets(FileKey &stickersKey, CheckSet checkSet, const Stickers::Order &order) {
 | |
| 	if (!_working()) return;
 | |
| 
 | |
| 	const auto &sets = Auth().data().stickerSets();
 | |
| 	if (sets.isEmpty()) {
 | |
| 		if (stickersKey) {
 | |
| 			clearKey(stickersKey);
 | |
| 			stickersKey = 0;
 | |
| 			_mapChanged = true;
 | |
| 		}
 | |
| 		_writeMap();
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	// versionTag + version + count
 | |
| 	quint32 size = sizeof(quint32) + sizeof(qint32) + sizeof(qint32);
 | |
| 
 | |
| 	int32 setsCount = 0;
 | |
| 	for (const auto &set : sets) {
 | |
| 		auto result = checkSet(set);
 | |
| 		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(set.title)
 | |
| 			+ Serialize::stringSize(set.shortName)
 | |
| 			+ sizeof(qint32) * 4
 | |
| 			+ Serialize::storageImageLocationSize(set.thumbnail
 | |
| 				? set.thumbnail->location()
 | |
| 				: StorageImageLocation());
 | |
| 		if (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded) {
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		for (const auto sticker : set.stickers) {
 | |
| 			sticker->refreshStickerThumbFileReference();
 | |
| 			size += Serialize::Document::sizeInStream(sticker);
 | |
| 		}
 | |
| 
 | |
| 		size += sizeof(qint32); // datesCount
 | |
| 		if (!set.dates.empty()) {
 | |
| 			Assert(set.stickers.size() == set.dates.size());
 | |
| 			size += set.dates.size() * sizeof(qint32);
 | |
| 		}
 | |
| 
 | |
| 		size += sizeof(qint32); // emojiCount
 | |
| 		for (auto j = set.emoji.cbegin(), e = set.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);
 | |
| 			stickersKey = 0;
 | |
| 			_mapChanged = true;
 | |
| 		}
 | |
| 		_writeMap();
 | |
| 		return;
 | |
| 	}
 | |
| 	size += sizeof(qint32) + (order.size() * sizeof(quint64));
 | |
| 
 | |
| 	if (!stickersKey) {
 | |
| 		stickersKey = genKey();
 | |
| 		_mapChanged = true;
 | |
| 		_writeMap(WriteMapWhen::Fast);
 | |
| 	}
 | |
| 	EncryptedDescriptor data(size);
 | |
| 	data.stream
 | |
| 		<< quint32(kStickersVersionTag)
 | |
| 		<< qint32(kStickersSerializeVersion)
 | |
| 		<< qint32(setsCount);
 | |
| 	for (const auto &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);
 | |
| 	file.writeEncrypted(data);
 | |
| }
 | |
| 
 | |
| void _readStickerSets(FileKey &stickersKey, Stickers::Order *outOrder = nullptr, MTPDstickerSet::Flags readingFlags = 0) {
 | |
| 	FileReadDescriptor stickers;
 | |
| 	if (!readEncryptedFile(stickers, stickersKey)) {
 | |
| 		clearKey(stickersKey);
 | |
| 		stickersKey = 0;
 | |
| 		_writeMap();
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	const auto failed = [&] {
 | |
| 		clearKey(stickersKey);
 | |
| 		stickersKey = 0;
 | |
| 	};
 | |
| 
 | |
| 	auto &sets = Auth().data().stickerSetsRef();
 | |
| 	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) {
 | |
| 		using LocationType = StorageFileLocation::Type;
 | |
| 
 | |
| 		quint64 setId = 0, setAccess = 0;
 | |
| 		QString setTitle, setShortName;
 | |
| 		qint32 scnt = 0;
 | |
| 		qint32 setInstallDate = 0;
 | |
| 		qint32 setHash = 0;
 | |
| 		MTPDstickerSet::Flags setFlags = 0;
 | |
| 		qint32 setFlagsValue = 0;
 | |
| 		StorageImageLocation setThumbnail;
 | |
| 
 | |
| 		stickers.stream
 | |
| 			>> setId
 | |
| 			>> setAccess
 | |
| 			>> setTitle
 | |
| 			>> setShortName
 | |
| 			>> scnt
 | |
| 			>> setHash
 | |
| 			>> setFlagsValue
 | |
| 			>> setInstallDate;
 | |
| 		const auto thumbnail = Serialize::readStorageImageLocation(
 | |
| 			stickers.version,
 | |
| 			stickers.stream);
 | |
| 		if (!thumbnail || !_checkStreamStatus(stickers.stream)) {
 | |
| 			return failed();
 | |
| 		} else if (thumbnail->valid()
 | |
| 			&& thumbnail->type() == LocationType::Legacy) {
 | |
| 			setThumbnail = thumbnail->convertToModern(
 | |
| 				LocationType::StickerSetThumb,
 | |
| 				setId,
 | |
| 				setAccess);
 | |
| 		} else {
 | |
| 			setThumbnail = *thumbnail;
 | |
| 		}
 | |
| 
 | |
| 		setFlags = MTPDstickerSet::Flags::from_raw(setFlagsValue);
 | |
| 		if (setId == Stickers::DefaultSetId) {
 | |
| 			setTitle = tr::lng_stickers_default_set(tr::now);
 | |
| 			setFlags |= MTPDstickerSet::Flag::f_official | MTPDstickerSet_ClientFlag::f_special;
 | |
| 		} else if (setId == Stickers::CustomSetId) {
 | |
| 			setTitle = qsl("Custom stickers");
 | |
| 			setFlags |= MTPDstickerSet_ClientFlag::f_special;
 | |
| 		} else if (setId == Stickers::CloudRecentSetId) {
 | |
| 			setTitle = tr::lng_recent_stickers(tr::now);
 | |
| 			setFlags |= MTPDstickerSet_ClientFlag::f_special;
 | |
| 		} else if (setId == Stickers::FavedSetId) {
 | |
| 			setTitle = Lang::Hard::FavedSetTitle();
 | |
| 			setFlags |= MTPDstickerSet_ClientFlag::f_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 &= ~(MTPDstickerSet::Flag::f_installed_date | MTPDstickerSet_ClientFlag::f_featured);
 | |
| 			it = sets.insert(setId, Stickers::Set(
 | |
| 				setId,
 | |
| 				setAccess,
 | |
| 				setTitle,
 | |
| 				setShortName,
 | |
| 				0,
 | |
| 				setHash,
 | |
| 				MTPDstickerSet::Flags(setFlags),
 | |
| 				setInstallDate,
 | |
| 				Images::CreateStickerSetThumbnail(setThumbnail)));
 | |
| 		}
 | |
| 		auto &set = it.value();
 | |
| 		auto inputSet = MTP_inputStickerSetID(MTP_long(set.id), MTP_long(set.access));
 | |
| 		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(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 & MTPDstickerSet_ClientFlag::f_special)) {
 | |
| 					if (document->sticker()->set.type() != mtpc_inputStickerSetID) {
 | |
| 						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 == Stickers::CloudRecentSetId)
 | |
| 				&& (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;
 | |
| 			Stickers::Pack pack;
 | |
| 			pack.reserve(stickersCount);
 | |
| 			for (int32 k = 0; k < stickersCount; ++k) {
 | |
| 				quint64 id;
 | |
| 				stickers.stream >> id;
 | |
| 				const auto doc = Auth().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) {
 | |
| 		stickers.stream >> *outOrder;
 | |
| 	}
 | |
| 	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()) {
 | |
| 				it->flags |= readingFlags;
 | |
| 				if ((readingFlags == MTPDstickerSet::Flag::f_installed_date)
 | |
| 					&& !it->installDate) {
 | |
| 					it->installDate = kDefaultStickerInstallDate;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void writeInstalledStickers() {
 | |
| 	if (!Global::started()) return;
 | |
| 
 | |
| 	_writeStickerSets(_installedStickersKey, [](const Stickers::Set &set) {
 | |
| 		if (set.id == Stickers::CloudRecentSetId || set.id == Stickers::FavedSetId) { // separate files for them
 | |
| 			return StickerSetCheckResult::Skip;
 | |
| 		} else if (set.flags & MTPDstickerSet_ClientFlag::f_special) {
 | |
| 			if (set.stickers.isEmpty()) { // all other special are "installed"
 | |
| 				return StickerSetCheckResult::Skip;
 | |
| 			}
 | |
| 		} else if (!(set.flags & MTPDstickerSet::Flag::f_installed_date) || (set.flags & MTPDstickerSet::Flag::f_archived)) {
 | |
| 			return StickerSetCheckResult::Skip;
 | |
| 		} else if (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded) { // waiting to receive
 | |
| 			return StickerSetCheckResult::Abort;
 | |
| 		} else if (set.stickers.isEmpty()) {
 | |
| 			return StickerSetCheckResult::Skip;
 | |
| 		}
 | |
| 		return StickerSetCheckResult::Write;
 | |
| 	}, Auth().data().stickerSetsOrder());
 | |
| }
 | |
| 
 | |
| void writeFeaturedStickers() {
 | |
| 	if (!Global::started()) return;
 | |
| 
 | |
| 	_writeStickerSets(_featuredStickersKey, [](const Stickers::Set &set) {
 | |
| 		if (set.id == Stickers::CloudRecentSetId || set.id == Stickers::FavedSetId) { // separate files for them
 | |
| 			return StickerSetCheckResult::Skip;
 | |
| 		} else if (set.flags & MTPDstickerSet_ClientFlag::f_special) {
 | |
| 			return StickerSetCheckResult::Skip;
 | |
| 		} else if (!(set.flags & MTPDstickerSet_ClientFlag::f_featured)) {
 | |
| 			return StickerSetCheckResult::Skip;
 | |
| 		} else if (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded) { // waiting to receive
 | |
| 			return StickerSetCheckResult::Abort;
 | |
| 		} else if (set.stickers.isEmpty()) {
 | |
| 			return StickerSetCheckResult::Skip;
 | |
| 		}
 | |
| 		return StickerSetCheckResult::Write;
 | |
| 	}, Auth().data().featuredStickerSetsOrder());
 | |
| }
 | |
| 
 | |
| void writeRecentStickers() {
 | |
| 	if (!Global::started()) return;
 | |
| 
 | |
| 	_writeStickerSets(_recentStickersKey, [](const Stickers::Set &set) {
 | |
| 		if (set.id != Stickers::CloudRecentSetId || set.stickers.isEmpty()) {
 | |
| 			return StickerSetCheckResult::Skip;
 | |
| 		}
 | |
| 		return StickerSetCheckResult::Write;
 | |
| 	}, Stickers::Order());
 | |
| }
 | |
| 
 | |
| void writeFavedStickers() {
 | |
| 	if (!Global::started()) return;
 | |
| 
 | |
| 	_writeStickerSets(_favedStickersKey, [](const Stickers::Set &set) {
 | |
| 		if (set.id != Stickers::FavedSetId || set.stickers.isEmpty()) {
 | |
| 			return StickerSetCheckResult::Skip;
 | |
| 		}
 | |
| 		return StickerSetCheckResult::Write;
 | |
| 	}, Stickers::Order());
 | |
| }
 | |
| 
 | |
| void writeArchivedStickers() {
 | |
| 	if (!Global::started()) return;
 | |
| 
 | |
| 	_writeStickerSets(_archivedStickersKey, [](const Stickers::Set &set) {
 | |
| 		if (!(set.flags & MTPDstickerSet::Flag::f_archived) || set.stickers.isEmpty()) {
 | |
| 			return StickerSetCheckResult::Skip;
 | |
| 		}
 | |
| 		return StickerSetCheckResult::Write;
 | |
| 	}, Auth().data().archivedStickerSetsOrder());
 | |
| }
 | |
| 
 | |
| void importOldRecentStickers() {
 | |
| 	if (!_recentStickersKeyOld) return;
 | |
| 
 | |
| 	FileReadDescriptor stickers;
 | |
| 	if (!readEncryptedFile(stickers, _recentStickersKeyOld)) {
 | |
| 		clearKey(_recentStickersKeyOld);
 | |
| 		_recentStickersKeyOld = 0;
 | |
| 		_writeMap();
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	auto &sets = Auth().data().stickerSetsRef();
 | |
| 	sets.clear();
 | |
| 
 | |
| 	auto &order = Auth().data().stickerSetsOrderRef();
 | |
| 	order.clear();
 | |
| 
 | |
| 	auto &recent = cRefRecentStickers();
 | |
| 	recent.clear();
 | |
| 
 | |
| 	auto &def = sets.insert(Stickers::DefaultSetId, Stickers::Set(
 | |
| 		Stickers::DefaultSetId,
 | |
| 		uint64(0),
 | |
| 		tr::lng_stickers_default_set(tr::now),
 | |
| 		QString(),
 | |
| 		0, // count
 | |
| 		0, // hash
 | |
| 		(MTPDstickerSet::Flag::f_official
 | |
| 			| MTPDstickerSet::Flag::f_installed_date
 | |
| 			| MTPDstickerSet_ClientFlag::f_special),
 | |
| 		kDefaultStickerInstallDate,
 | |
| 		ImagePtr())).value();
 | |
| 	auto &custom = sets.insert(Stickers::CustomSetId, Stickers::Set(
 | |
| 		Stickers::CustomSetId,
 | |
| 		uint64(0),
 | |
| 		qsl("Custom stickers"),
 | |
| 		QString(),
 | |
| 		0, // count
 | |
| 		0, // hash
 | |
| 		(MTPDstickerSet::Flag::f_installed_date
 | |
| 			| MTPDstickerSet_ClientFlag::f_special),
 | |
| 		kDefaultStickerInstallDate,
 | |
| 		ImagePtr())).value();
 | |
| 
 | |
| 	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 = Auth().data().document(
 | |
| 			id,
 | |
| 			access,
 | |
| 			QByteArray(),
 | |
| 			date,
 | |
| 			attributes,
 | |
| 			mime,
 | |
| 			ImagePtr(),
 | |
| 			ImagePtr(),
 | |
| 			dc,
 | |
| 			size,
 | |
| 			StorageImageLocation());
 | |
| 		if (!doc->sticker()) continue;
 | |
| 
 | |
| 		if (value > 0) {
 | |
| 			def.stickers.push_back(doc);
 | |
| 			++def.count;
 | |
| 		} else {
 | |
| 			custom.stickers.push_back(doc);
 | |
| 			++custom.count;
 | |
| 		}
 | |
| 		if (recent.size() < Global::StickersRecentLimit() && qAbs(value) > 1) {
 | |
| 			recent.push_back(qMakePair(doc, qAbs(value)));
 | |
| 		}
 | |
| 	}
 | |
| 	if (def.stickers.isEmpty()) {
 | |
| 		sets.remove(Stickers::DefaultSetId);
 | |
| 	} else {
 | |
| 		order.push_front(Stickers::DefaultSetId);
 | |
| 	}
 | |
| 	if (custom.stickers.isEmpty()) sets.remove(Stickers::CustomSetId);
 | |
| 
 | |
| 	writeInstalledStickers();
 | |
| 	writeUserSettings();
 | |
| 
 | |
| 	clearKey(_recentStickersKeyOld);
 | |
| 	_recentStickersKeyOld = 0;
 | |
| 	_writeMap();
 | |
| }
 | |
| 
 | |
| void readInstalledStickers() {
 | |
| 	if (!_installedStickersKey) {
 | |
| 		return importOldRecentStickers();
 | |
| 	}
 | |
| 
 | |
| 	Auth().data().stickerSetsRef().clear();
 | |
| 	_readStickerSets(
 | |
| 		_installedStickersKey,
 | |
| 		&Auth().data().stickerSetsOrderRef(),
 | |
| 		MTPDstickerSet::Flag::f_installed_date);
 | |
| }
 | |
| 
 | |
| void readFeaturedStickers() {
 | |
| 	_readStickerSets(
 | |
| 		_featuredStickersKey,
 | |
| 		&Auth().data().featuredStickerSetsOrderRef(),
 | |
| 		MTPDstickerSet::Flags() | MTPDstickerSet_ClientFlag::f_featured);
 | |
| 
 | |
| 	auto &sets = Auth().data().stickerSets();
 | |
| 	int unreadCount = 0;
 | |
| 	for_const (auto setId, Auth().data().featuredStickerSetsOrder()) {
 | |
| 		auto it = sets.constFind(setId);
 | |
| 		if (it != sets.cend() && (it->flags & MTPDstickerSet_ClientFlag::f_unread)) {
 | |
| 			++unreadCount;
 | |
| 		}
 | |
| 	}
 | |
| 	Auth().data().setFeaturedStickerSetsUnreadCount(unreadCount);
 | |
| }
 | |
| 
 | |
| void readRecentStickers() {
 | |
| 	_readStickerSets(_recentStickersKey);
 | |
| }
 | |
| 
 | |
| void readFavedStickers() {
 | |
| 	_readStickerSets(_favedStickersKey);
 | |
| }
 | |
| 
 | |
| void readArchivedStickers() {
 | |
| 	static bool archivedStickersRead = false;
 | |
| 	if (!archivedStickersRead) {
 | |
| 		_readStickerSets(_archivedStickersKey, &Auth().data().archivedStickerSetsOrderRef());
 | |
| 		archivedStickersRead = true;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| int32 countDocumentVectorHash(const QVector<DocumentData*> vector) {
 | |
| 	auto result = Api::HashInit();
 | |
| 	for (const auto document : vector) {
 | |
| 		Api::HashUpdate(result, document->id);
 | |
| 	}
 | |
| 	return Api::HashFinalize(result);
 | |
| }
 | |
| 
 | |
| int32 countSpecialStickerSetHash(uint64 setId) {
 | |
| 	auto &sets = Auth().data().stickerSets();
 | |
| 	auto it = sets.constFind(setId);
 | |
| 	if (it != sets.cend()) {
 | |
| 		return countDocumentVectorHash(it->stickers);
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int32 countStickersHash(bool checkOutdatedInfo) {
 | |
| 	auto result = Api::HashInit();
 | |
| 	bool foundOutdated = false;
 | |
| 	auto &sets = Auth().data().stickerSets();
 | |
| 	auto &order = Auth().data().stickerSetsOrder();
 | |
| 	for (auto i = order.cbegin(), e = order.cend(); i != e; ++i) {
 | |
| 		auto j = sets.constFind(*i);
 | |
| 		if (j != sets.cend()) {
 | |
| 			if (j->id == Stickers::DefaultSetId) {
 | |
| 				foundOutdated = true;
 | |
| 			} else if (!(j->flags & MTPDstickerSet_ClientFlag::f_special)
 | |
| 				&& !(j->flags & MTPDstickerSet::Flag::f_archived)) {
 | |
| 				Api::HashUpdate(result, j->hash);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return (!checkOutdatedInfo || !foundOutdated)
 | |
| 		? Api::HashFinalize(result)
 | |
| 		: 0;
 | |
| }
 | |
| 
 | |
| int32 countRecentStickersHash() {
 | |
| 	return countSpecialStickerSetHash(Stickers::CloudRecentSetId);
 | |
| }
 | |
| 
 | |
| int32 countFavedStickersHash() {
 | |
| 	return countSpecialStickerSetHash(Stickers::FavedSetId);
 | |
| }
 | |
| 
 | |
| int32 countFeaturedStickersHash() {
 | |
| 	auto result = Api::HashInit();
 | |
| 	const auto &sets = Auth().data().stickerSets();
 | |
| 	const auto &featured = Auth().data().featuredStickerSetsOrder();
 | |
| 	for (const auto setId : featured) {
 | |
| 		Api::HashUpdate(result, setId);
 | |
| 
 | |
| 		auto it = sets.constFind(setId);
 | |
| 		if (it != sets.cend() && (it->flags & MTPDstickerSet_ClientFlag::f_unread)) {
 | |
| 			Api::HashUpdate(result, 1);
 | |
| 		}
 | |
| 	}
 | |
| 	return Api::HashFinalize(result);
 | |
| }
 | |
| 
 | |
| int32 countSavedGifsHash() {
 | |
| 	return countDocumentVectorHash(Auth().data().savedGifs());
 | |
| }
 | |
| 
 | |
| void writeSavedGifs() {
 | |
| 	if (!_working()) return;
 | |
| 
 | |
| 	auto &saved = Auth().data().savedGifs();
 | |
| 	if (saved.isEmpty()) {
 | |
| 		if (_savedGifsKey) {
 | |
| 			clearKey(_savedGifsKey);
 | |
| 			_savedGifsKey = 0;
 | |
| 			_mapChanged = true;
 | |
| 		}
 | |
| 		_writeMap();
 | |
| 	} else {
 | |
| 		quint32 size = sizeof(quint32); // count
 | |
| 		for_const (auto gif, saved) {
 | |
| 			size += Serialize::Document::sizeInStream(gif);
 | |
| 		}
 | |
| 
 | |
| 		if (!_savedGifsKey) {
 | |
| 			_savedGifsKey = genKey();
 | |
| 			_mapChanged = true;
 | |
| 			_writeMap(WriteMapWhen::Fast);
 | |
| 		}
 | |
| 		EncryptedDescriptor data(size);
 | |
| 		data.stream << quint32(saved.size());
 | |
| 		for_const (auto gif, saved) {
 | |
| 			Serialize::Document::writeToStream(data.stream, gif);
 | |
| 		}
 | |
| 		FileWriteDescriptor file(_savedGifsKey);
 | |
| 		file.writeEncrypted(data);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void readSavedGifs() {
 | |
| 	if (!_savedGifsKey) return;
 | |
| 
 | |
| 	FileReadDescriptor gifs;
 | |
| 	if (!readEncryptedFile(gifs, _savedGifsKey)) {
 | |
| 		clearKey(_savedGifsKey);
 | |
| 		_savedGifsKey = 0;
 | |
| 		_writeMap();
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	auto &saved = Auth().data().savedGifsRef();
 | |
| 	const auto failed = [&] {
 | |
| 		clearKey(_savedGifsKey);
 | |
| 		_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(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 writeBackground(const Data::WallPaper &paper, const QImage &image) {
 | |
| 	if (!_working() || !_backgroundCanWrite) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (!LocalKey) {
 | |
| 		LOG(("App Error: localkey not created in writeBackground()"));
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	auto &backgroundKey = Window::Theme::IsNightMode()
 | |
| 		? _backgroundKeyNight
 | |
| 		: _backgroundKeyDay;
 | |
| 	auto imageData = QByteArray();
 | |
| 	if (!image.isNull()) {
 | |
| 		const auto width = qint32(image.width());
 | |
| 		const auto height = qint32(image.height());
 | |
| 		const auto perpixel = (image.depth() >> 3);
 | |
| 		const auto srcperline = image.bytesPerLine();
 | |
| 		const auto srcsize = srcperline * height;
 | |
| 		const auto dstperline = width * perpixel;
 | |
| 		const auto dstsize = dstperline * height;
 | |
| 		const auto copy = (image.format() != kSavedBackgroundFormat)
 | |
| 			? image.convertToFormat(kSavedBackgroundFormat)
 | |
| 			: image;
 | |
| 		imageData.resize(2 * sizeof(qint32) + dstsize);
 | |
| 
 | |
| 		auto dst = bytes::make_detached_span(imageData);
 | |
| 		bytes::copy(dst, bytes::object_as_span(&width));
 | |
| 		dst = dst.subspan(sizeof(qint32));
 | |
| 		bytes::copy(dst, bytes::object_as_span(&height));
 | |
| 		dst = dst.subspan(sizeof(qint32));
 | |
| 		const auto src = bytes::make_span(image.constBits(), srcsize);
 | |
| 		if (srcsize == dstsize) {
 | |
| 			bytes::copy(dst, src);
 | |
| 		} else {
 | |
| 			for (auto y = 0; y != height; ++y) {
 | |
| 				bytes::copy(dst, src.subspan(y * srcperline, dstperline));
 | |
| 				dst = dst.subspan(dstperline);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if (!backgroundKey) {
 | |
| 		backgroundKey = genKey();
 | |
| 		_mapChanged = true;
 | |
| 		_writeMap(WriteMapWhen::Fast);
 | |
| 	}
 | |
| 	const auto serialized = paper.serialize();
 | |
| 	quint32 size = sizeof(qint32)
 | |
| 		+ Serialize::bytearraySize(serialized)
 | |
| 		+ Serialize::bytearraySize(imageData);
 | |
| 	EncryptedDescriptor data(size);
 | |
| 	data.stream
 | |
| 		<< qint32(kWallPaperSerializeTagId)
 | |
| 		<< serialized
 | |
| 		<< imageData;
 | |
| 
 | |
| 	FileWriteDescriptor file(backgroundKey);
 | |
| 	file.writeEncrypted(data);
 | |
| }
 | |
| 
 | |
| bool readBackground() {
 | |
| 	FileReadDescriptor bg;
 | |
| 	auto &backgroundKey = Window::Theme::IsNightMode()
 | |
| 		? _backgroundKeyNight
 | |
| 		: _backgroundKeyDay;
 | |
| 	if (!readEncryptedFile(bg, backgroundKey)) {
 | |
| 		if (backgroundKey) {
 | |
| 			clearKey(backgroundKey);
 | |
| 			backgroundKey = 0;
 | |
| 			_mapChanged = true;
 | |
| 			_writeMap();
 | |
| 		}
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	qint32 legacyId = 0;
 | |
| 	bg.stream >> legacyId;
 | |
| 	const auto paper = [&] {
 | |
| 		if (legacyId == kWallPaperLegacySerializeTagId) {
 | |
| 			quint64 id = 0;
 | |
| 			quint64 accessHash = 0;
 | |
| 			quint32 flags = 0;
 | |
| 			QString slug;
 | |
| 			bg.stream
 | |
| 				>> id
 | |
| 				>> accessHash
 | |
| 				>> flags
 | |
| 				>> slug;
 | |
| 			return Data::WallPaper::FromLegacySerialized(
 | |
| 				id,
 | |
| 				accessHash,
 | |
| 				flags,
 | |
| 				slug);
 | |
| 		} else if (legacyId == kWallPaperSerializeTagId) {
 | |
| 			QByteArray serialized;
 | |
| 			bg.stream >> serialized;
 | |
| 			return Data::WallPaper::FromSerialized(serialized);
 | |
| 		} else {
 | |
| 			return Data::WallPaper::FromLegacyId(legacyId);
 | |
| 		}
 | |
| 	}();
 | |
| 	if (bg.stream.status() != QDataStream::Ok || !paper) {
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	QByteArray imageData;
 | |
| 	bg.stream >> imageData;
 | |
| 	const auto isOldEmptyImage = (bg.stream.status() != QDataStream::Ok);
 | |
| 	if (isOldEmptyImage
 | |
| 		|| Data::IsLegacy1DefaultWallPaper(*paper)
 | |
| 		|| Data::IsDefaultWallPaper(*paper)) {
 | |
| 		_backgroundCanWrite = false;
 | |
| 		if (isOldEmptyImage || bg.version < 8005) {
 | |
| 			Window::Theme::Background()->set(Data::DefaultWallPaper());
 | |
| 			Window::Theme::Background()->setTile(false);
 | |
| 		} else {
 | |
| 			Window::Theme::Background()->set(*paper);
 | |
| 		}
 | |
| 		_backgroundCanWrite = true;
 | |
| 		return true;
 | |
| 	} else if (Data::IsThemeWallPaper(*paper) && imageData.isEmpty()) {
 | |
| 		_backgroundCanWrite = false;
 | |
| 		Window::Theme::Background()->set(*paper);
 | |
| 		_backgroundCanWrite = true;
 | |
| 		return true;
 | |
| 	}
 | |
| 	auto image = QImage();
 | |
| 	if (legacyId == kWallPaperSerializeTagId) {
 | |
| 		const auto perpixel = 4;
 | |
| 		auto src = bytes::make_span(imageData);
 | |
| 		auto width = qint32();
 | |
| 		auto height = qint32();
 | |
| 		if (src.size() > 2 * sizeof(qint32)) {
 | |
| 			bytes::copy(
 | |
| 				bytes::object_as_span(&width),
 | |
| 				src.subspan(0, sizeof(qint32)));
 | |
| 			src = src.subspan(sizeof(qint32));
 | |
| 			bytes::copy(
 | |
| 				bytes::object_as_span(&height),
 | |
| 				src.subspan(0, sizeof(qint32)));
 | |
| 			src = src.subspan(sizeof(qint32));
 | |
| 			if (width + height <= kWallPaperSidesLimit
 | |
| 				&& src.size() == width * height * perpixel) {
 | |
| 				image = QImage(
 | |
| 					width,
 | |
| 					height,
 | |
| 					QImage::Format_ARGB32_Premultiplied);
 | |
| 				if (!image.isNull()) {
 | |
| 					const auto srcperline = width * perpixel;
 | |
| 					const auto srcsize = srcperline * height;
 | |
| 					const auto dstperline = image.bytesPerLine();
 | |
| 					const auto dstsize = dstperline * height;
 | |
| 					Assert(srcsize == dstsize);
 | |
| 					bytes::copy(
 | |
| 						bytes::make_span(image.bits(), dstsize),
 | |
| 						src);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	} else {
 | |
| 		auto buffer = QBuffer(&imageData);
 | |
| 		auto reader = QImageReader(&buffer);
 | |
| #ifndef OS_MAC_OLD
 | |
| 		reader.setAutoTransform(true);
 | |
| #endif // OS_MAC_OLD
 | |
| 		if (!reader.read(&image)) {
 | |
| 			image = QImage();
 | |
| 		}
 | |
| 	}
 | |
| 	if (!image.isNull() || paper->backgroundColor()) {
 | |
| 		_backgroundCanWrite = false;
 | |
| 		Window::Theme::Background()->set(*paper, std::move(image));
 | |
| 		_backgroundCanWrite = true;
 | |
| 		return true;
 | |
| 	}
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| Window::Theme::Saved readThemeUsingKey(FileKey key) {
 | |
| 	using namespace Window::Theme;
 | |
| 
 | |
| 	FileReadDescriptor theme;
 | |
| 	if (!readEncryptedFile(theme, key, FileOption::Safe, SettingsKey)) {
 | |
| 		return {};
 | |
| 	}
 | |
| 
 | |
| 	auto tag = QString();
 | |
| 	auto result = Saved();
 | |
| 	auto &object = result.object;
 | |
| 	auto &cache = result.cache;
 | |
| 	theme.stream >> object.content;
 | |
| 	theme.stream >> tag >> object.pathAbsolute;
 | |
| 	if (tag == kThemeNewPathRelativeTag) {
 | |
| 		auto creator = qint32();
 | |
| 		theme.stream
 | |
| 			>> object.pathRelative
 | |
| 			>> object.cloud.id
 | |
| 			>> object.cloud.accessHash
 | |
| 			>> object.cloud.slug
 | |
| 			>> object.cloud.title
 | |
| 			>> object.cloud.documentId
 | |
| 			>> creator;
 | |
| 		object.cloud.createdBy = creator;
 | |
| 	} else {
 | |
| 		object.pathRelative = tag;
 | |
| 	}
 | |
| 	if (theme.stream.status() != QDataStream::Ok) {
 | |
| 		return {};
 | |
| 	}
 | |
| 
 | |
| 	auto ignoreCache = false;
 | |
| 	if (!object.cloud.id) {
 | |
| 		auto file = QFile(object.pathRelative);
 | |
| 		if (object.pathRelative.isEmpty() || !file.exists()) {
 | |
| 			file.setFileName(object.pathAbsolute);
 | |
| 		}
 | |
| 		if (!file.fileName().isEmpty()
 | |
| 			&& file.exists()
 | |
| 			&& file.open(QIODevice::ReadOnly)) {
 | |
| 			if (file.size() > kThemeFileSizeLimit) {
 | |
| 				LOG(("Error: theme file too large: %1 "
 | |
| 					"(should be less than 5 MB, got %2)"
 | |
| 					).arg(file.fileName()
 | |
| 					).arg(file.size()));
 | |
| 				return {};
 | |
| 			}
 | |
| 			auto fileContent = file.readAll();
 | |
| 			file.close();
 | |
| 			if (object.content != fileContent) {
 | |
| 				object.content = fileContent;
 | |
| 				ignoreCache = true;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if (!ignoreCache) {
 | |
| 		quint32 backgroundIsTiled = 0;
 | |
| 		theme.stream
 | |
| 			>> cache.paletteChecksum
 | |
| 			>> cache.contentChecksum
 | |
| 			>> cache.colors
 | |
| 			>> cache.background
 | |
| 			>> backgroundIsTiled;
 | |
| 		cache.tiled = (backgroundIsTiled == 1);
 | |
| 		if (theme.stream.status() != QDataStream::Ok) {
 | |
| 			return {};
 | |
| 		}
 | |
| 	}
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| std::optional<QString> InitialLoadThemeUsingKey(FileKey key) {
 | |
| 	auto read = readThemeUsingKey(key);
 | |
| 	const auto result = read.object.pathAbsolute;
 | |
| 	if (read.object.content.isEmpty()
 | |
| 		|| !Window::Theme::Initialize(std::move(read))) {
 | |
| 		return std::nullopt;
 | |
| 	}
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| void writeTheme(const Window::Theme::Saved &saved) {
 | |
| 	using namespace Window::Theme;
 | |
| 
 | |
| 	if (_themeKeyLegacy) {
 | |
| 		return;
 | |
| 	}
 | |
| 	auto &themeKey = IsNightMode()
 | |
| 		? _themeKeyNight
 | |
| 		: _themeKeyDay;
 | |
| 	if (saved.object.content.isEmpty()) {
 | |
| 		if (themeKey) {
 | |
| 			clearKey(themeKey);
 | |
| 			themeKey = 0;
 | |
| 			writeSettings();
 | |
| 		}
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (!themeKey) {
 | |
| 		themeKey = genKey(FileOption::Safe);
 | |
| 		writeSettings();
 | |
| 	}
 | |
| 
 | |
| 	const auto &object = saved.object;
 | |
| 	const auto &cache = saved.cache;
 | |
| 	const auto tag = QString(kThemeNewPathRelativeTag);
 | |
| 	quint32 size = Serialize::bytearraySize(object.content)
 | |
| 		+ Serialize::stringSize(tag)
 | |
| 		+ Serialize::stringSize(object.pathAbsolute)
 | |
| 		+ Serialize::stringSize(object.pathRelative)
 | |
| 		+ sizeof(uint64) * 3
 | |
| 		+ Serialize::stringSize(object.cloud.slug)
 | |
| 		+ Serialize::stringSize(object.cloud.title)
 | |
| 		+ sizeof(qint32)
 | |
| 		+ sizeof(qint32) * 2
 | |
| 		+ Serialize::bytearraySize(cache.colors)
 | |
| 		+ Serialize::bytearraySize(cache.background)
 | |
| 		+ sizeof(quint32);
 | |
| 	EncryptedDescriptor data(size);
 | |
| 	data.stream
 | |
| 		<< object.content
 | |
| 		<< tag
 | |
| 		<< object.pathAbsolute
 | |
| 		<< object.pathRelative
 | |
| 		<< object.cloud.id
 | |
| 		<< object.cloud.accessHash
 | |
| 		<< object.cloud.slug
 | |
| 		<< object.cloud.title
 | |
| 		<< object.cloud.documentId
 | |
| 		<< qint32(object.cloud.createdBy)
 | |
| 		<< cache.paletteChecksum
 | |
| 		<< cache.contentChecksum
 | |
| 		<< cache.colors
 | |
| 		<< cache.background
 | |
| 		<< quint32(cache.tiled ? 1 : 0);
 | |
| 
 | |
| 	FileWriteDescriptor file(themeKey, FileOption::Safe);
 | |
| 	file.writeEncrypted(data, SettingsKey);
 | |
| }
 | |
| 
 | |
| void clearTheme() {
 | |
| 	writeTheme(Window::Theme::Saved());
 | |
| }
 | |
| 
 | |
| void InitialLoadTheme() {
 | |
| 	const auto key = (_themeKeyLegacy != 0)
 | |
| 		? _themeKeyLegacy
 | |
| 		: (Window::Theme::IsNightMode()
 | |
| 			? _themeKeyNight
 | |
| 			: _themeKeyDay);
 | |
| 	if (!key) {
 | |
| 		return;
 | |
| 	} else if (const auto path = InitialLoadThemeUsingKey(key)) {
 | |
| 		if (_themeKeyLegacy) {
 | |
| 			Window::Theme::SetNightModeValue(*path
 | |
| 				== Window::Theme::NightThemePath());
 | |
| 			(Window::Theme::IsNightMode()
 | |
| 				? _themeKeyNight
 | |
| 				: _themeKeyDay) = base::take(_themeKeyLegacy);
 | |
| 		}
 | |
| 	} else {
 | |
| 		clearTheme();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| Window::Theme::Saved readThemeAfterSwitch() {
 | |
| 	const auto key = Window::Theme::IsNightMode()
 | |
| 		? _themeKeyNight
 | |
| 		: _themeKeyDay;
 | |
| 	return readThemeUsingKey(key);
 | |
| }
 | |
| 
 | |
| void readLangPack() {
 | |
| 	FileReadDescriptor langpack;
 | |
| 	if (!_langPackKey || !readEncryptedFile(langpack, _langPackKey, FileOption::Safe, SettingsKey)) {
 | |
| 		return;
 | |
| 	}
 | |
| 	auto data = QByteArray();
 | |
| 	langpack.stream >> data;
 | |
| 	if (langpack.stream.status() == QDataStream::Ok) {
 | |
| 		Lang::Current().fillFromSerialized(data, langpack.version);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void writeLangPack() {
 | |
| 	auto langpack = Lang::Current().serialize();
 | |
| 	if (!_langPackKey) {
 | |
| 		_langPackKey = genKey(FileOption::Safe);
 | |
| 		writeSettings();
 | |
| 	}
 | |
| 
 | |
| 	EncryptedDescriptor data(Serialize::bytearraySize(langpack));
 | |
| 	data.stream << langpack;
 | |
| 
 | |
| 	FileWriteDescriptor file(_langPackKey, FileOption::Safe);
 | |
| 	file.writeEncrypted(data, SettingsKey);
 | |
| }
 | |
| 
 | |
| void saveRecentLanguages(const std::vector<Lang::Language> &list) {
 | |
| 	if (list.empty()) {
 | |
| 		if (_languagesKey) {
 | |
| 			clearKey(_languagesKey, FileOption::Safe);
 | |
| 			_languagesKey = 0;
 | |
| 			writeSettings();
 | |
| 		}
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	auto size = sizeof(qint32);
 | |
| 	for (const auto &language : list) {
 | |
| 		size += Serialize::stringSize(language.id)
 | |
| 			+ Serialize::stringSize(language.pluralId)
 | |
| 			+ Serialize::stringSize(language.baseId)
 | |
| 			+ Serialize::stringSize(language.name)
 | |
| 			+ Serialize::stringSize(language.nativeName);
 | |
| 	}
 | |
| 	if (!_languagesKey) {
 | |
| 		_languagesKey = genKey(FileOption::Safe);
 | |
| 		writeSettings();
 | |
| 	}
 | |
| 
 | |
| 	EncryptedDescriptor data(size);
 | |
| 	data.stream << qint32(list.size());
 | |
| 	for (const auto &language : list) {
 | |
| 		data.stream
 | |
| 			<< language.id
 | |
| 			<< language.pluralId
 | |
| 			<< language.baseId
 | |
| 			<< language.name
 | |
| 			<< language.nativeName;
 | |
| 	}
 | |
| 
 | |
| 	FileWriteDescriptor file(_languagesKey, FileOption::Safe);
 | |
| 	file.writeEncrypted(data, SettingsKey);
 | |
| }
 | |
| 
 | |
| void pushRecentLanguage(const Lang::Language &language) {
 | |
| 	if (language.id.startsWith('#')) {
 | |
| 		return;
 | |
| 	}
 | |
| 	auto list = readRecentLanguages();
 | |
| 	list.erase(
 | |
| 		ranges::remove_if(
 | |
| 			list,
 | |
| 			[&](const Lang::Language &v) { return (v.id == language.id); }),
 | |
| 		end(list));
 | |
| 	list.insert(list.begin(), language);
 | |
| 
 | |
| 	saveRecentLanguages(list);
 | |
| }
 | |
| 
 | |
| void removeRecentLanguage(const QString &id) {
 | |
| 	auto list = readRecentLanguages();
 | |
| 	list.erase(
 | |
| 		ranges::remove_if(
 | |
| 			list,
 | |
| 			[&](const Lang::Language &v) { return (v.id == id); }),
 | |
| 		end(list));
 | |
| 
 | |
| 	saveRecentLanguages(list);
 | |
| }
 | |
| 
 | |
| std::vector<Lang::Language> readRecentLanguages() {
 | |
| 	FileReadDescriptor languages;
 | |
| 	if (!_languagesKey || !readEncryptedFile(languages, _languagesKey, FileOption::Safe, SettingsKey)) {
 | |
| 		return {};
 | |
| 	}
 | |
| 	qint32 count = 0;
 | |
| 	languages.stream >> count;
 | |
| 	if (count <= 0) {
 | |
| 		return {};
 | |
| 	}
 | |
| 	auto result = std::vector<Lang::Language>();
 | |
| 	result.reserve(count);
 | |
| 	for (auto i = 0; i != count; ++i) {
 | |
| 		auto language = Lang::Language();
 | |
| 		languages.stream
 | |
| 			>> language.id
 | |
| 			>> language.pluralId
 | |
| 			>> language.baseId
 | |
| 			>> language.name
 | |
| 			>> language.nativeName;
 | |
| 		result.push_back(language);
 | |
| 	}
 | |
| 	if (languages.stream.status() != QDataStream::Ok) {
 | |
| 		return {};
 | |
| 	}
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| Window::Theme::Object ReadThemeContent() {
 | |
| 	using namespace Window::Theme;
 | |
| 
 | |
| 	auto &themeKey = IsNightMode() ? _themeKeyNight : _themeKeyDay;
 | |
| 	if (!themeKey) {
 | |
| 		return Object();
 | |
| 	}
 | |
| 
 | |
| 	FileReadDescriptor theme;
 | |
| 	if (!readEncryptedFile(theme, themeKey, FileOption::Safe, SettingsKey)) {
 | |
| 		return Object();
 | |
| 	}
 | |
| 
 | |
| 	QByteArray content;
 | |
| 	QString pathRelative, pathAbsolute;
 | |
| 	theme.stream >> content >> pathRelative >> pathAbsolute;
 | |
| 	if (theme.stream.status() != QDataStream::Ok) {
 | |
| 		return Object();
 | |
| 	}
 | |
| 
 | |
| 	auto result = Object();
 | |
| 	result.pathAbsolute = pathAbsolute;
 | |
| 	result.content = content;
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| void writeRecentHashtagsAndBots() {
 | |
| 	if (!_working()) return;
 | |
| 
 | |
| 	const RecentHashtagPack &write(cRecentWriteHashtags()), &search(cRecentSearchHashtags());
 | |
| 	const RecentInlineBots &bots(cRecentInlineBots());
 | |
| 	if (write.isEmpty() && search.isEmpty() && bots.isEmpty()) readRecentHashtagsAndBots();
 | |
| 	if (write.isEmpty() && search.isEmpty() && bots.isEmpty()) {
 | |
| 		if (_recentHashtagsAndBotsKey) {
 | |
| 			clearKey(_recentHashtagsAndBotsKey);
 | |
| 			_recentHashtagsAndBotsKey = 0;
 | |
| 			_mapChanged = true;
 | |
| 		}
 | |
| 		_writeMap();
 | |
| 	} else {
 | |
| 		if (!_recentHashtagsAndBotsKey) {
 | |
| 			_recentHashtagsAndBotsKey = genKey();
 | |
| 			_mapChanged = true;
 | |
| 			_writeMap(WriteMapWhen::Fast);
 | |
| 		}
 | |
| 		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);
 | |
| 		file.writeEncrypted(data);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void readRecentHashtagsAndBots() {
 | |
| 	if (_recentHashtagsAndBotsWereRead) return;
 | |
| 	_recentHashtagsAndBotsWereRead = true;
 | |
| 
 | |
| 	if (!_recentHashtagsAndBotsKey) return;
 | |
| 
 | |
| 	FileReadDescriptor hashtags;
 | |
| 	if (!readEncryptedFile(hashtags, _recentHashtagsAndBotsKey)) {
 | |
| 		clearKey(_recentHashtagsAndBotsKey);
 | |
| 		_recentHashtagsAndBotsKey = 0;
 | |
| 		_writeMap();
 | |
| 		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(
 | |
| 					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);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void incrementRecentHashtag(RecentHashtagPack &recent, const QString &tag) {
 | |
| 	auto i = recent.begin(), e = recent.end();
 | |
| 	for (; i != e; ++i) {
 | |
| 		if (i->first == tag) {
 | |
| 			++i->second;
 | |
| 			if (qAbs(i->second) > 0x4000) {
 | |
| 				for (auto j = recent.begin(); j != e; ++j) {
 | |
| 					if (j->second > 1) {
 | |
| 						j->second /= 2;
 | |
| 					} else if (j->second > 0) {
 | |
| 						j->second = 1;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 			for (; i != recent.begin(); --i) {
 | |
| 				if (qAbs((i - 1)->second) > qAbs(i->second)) {
 | |
| 					break;
 | |
| 				}
 | |
| 				qSwap(*i, *(i - 1));
 | |
| 			}
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	if (i == e) {
 | |
| 		while (recent.size() >= 64) recent.pop_back();
 | |
| 		recent.push_back(qMakePair(tag, 1));
 | |
| 		for (i = recent.end() - 1; i != recent.begin(); --i) {
 | |
| 			if ((i - 1)->second > i->second) {
 | |
| 				break;
 | |
| 			}
 | |
| 			qSwap(*i, *(i - 1));
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| std::optional<RecentHashtagPack> 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()) {
 | |
| 			Local::readRecentHashtagsAndBots();
 | |
| 			recent = getPack();
 | |
| 		}
 | |
| 		found = true;
 | |
| 		incrementRecentHashtag(recent, tag);
 | |
| 	}
 | |
| 	return found ? base::make_optional(recent) : std::nullopt;
 | |
| }
 | |
| 
 | |
| void saveRecentSentHashtags(const QString &text) {
 | |
| 	const auto result = saveRecentHashtags(
 | |
| 		[] { return cRecentWriteHashtags(); },
 | |
| 		text);
 | |
| 	if (result) {
 | |
| 		cSetRecentWriteHashtags(*result);
 | |
| 		Local::writeRecentHashtagsAndBots();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void saveRecentSearchHashtags(const QString &text) {
 | |
| 	const auto result = saveRecentHashtags(
 | |
| 		[] { return cRecentSearchHashtags(); },
 | |
| 		text);
 | |
| 	if (result) {
 | |
| 		cSetRecentSearchHashtags(*result);
 | |
| 		Local::writeRecentHashtagsAndBots();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void WriteExportSettings(const Export::Settings &settings) {
 | |
| 	if (!_working()) return;
 | |
| 
 | |
| 	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);
 | |
| 			_exportSettingsKey = 0;
 | |
| 			_mapChanged = true;
 | |
| 		}
 | |
| 		_writeMap();
 | |
| 	} else {
 | |
| 		if (!_exportSettingsKey) {
 | |
| 			_exportSettingsKey = genKey();
 | |
| 			_mapChanged = true;
 | |
| 			_writeMap(WriteMapWhen::Fast);
 | |
| 		}
 | |
| 		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);
 | |
| 		file.writeEncrypted(data);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| Export::Settings ReadExportSettings() {
 | |
| 	FileReadDescriptor file;
 | |
| 	if (!readEncryptedFile(file, _exportSettingsKey)) {
 | |
| 		clearKey(_exportSettingsKey);
 | |
| 		_exportSettingsKey = 0;
 | |
| 		_writeMap();
 | |
| 		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 writeSelf() {
 | |
| 	_mapChanged = true;
 | |
| 	_writeMap();
 | |
| }
 | |
| 
 | |
| void readSelf(const QByteArray &serialized, int32 streamVersion) {
 | |
| 	QDataStream stream(serialized);
 | |
| 	const auto user = Auth().user();
 | |
| 	const auto wasLoadedStatus = std::exchange(
 | |
| 		user->loadedStatus,
 | |
| 		PeerData::NotLoaded);
 | |
| 	const auto self = Serialize::readPeer(streamVersion, stream);
 | |
| 	if (!self || !self->isSelf() || self != user) {
 | |
| 		user->loadedStatus = wasLoadedStatus;
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	QString about;
 | |
| 	stream >> about;
 | |
| 	if (_checkStreamStatus(stream)) {
 | |
| 		self->asUser()->setAbout(about);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void writeTrustedBots() {
 | |
| 	if (!_working()) return;
 | |
| 
 | |
| 	if (_trustedBots.isEmpty()) {
 | |
| 		if (_trustedBotsKey) {
 | |
| 			clearKey(_trustedBotsKey);
 | |
| 			_trustedBotsKey = 0;
 | |
| 			_mapChanged = true;
 | |
| 			_writeMap();
 | |
| 		}
 | |
| 	} else {
 | |
| 		if (!_trustedBotsKey) {
 | |
| 			_trustedBotsKey = genKey();
 | |
| 			_mapChanged = true;
 | |
| 			_writeMap(WriteMapWhen::Fast);
 | |
| 		}
 | |
| 		quint32 size = sizeof(qint32) + _trustedBots.size() * sizeof(quint64);
 | |
| 		EncryptedDescriptor data(size);
 | |
| 		data.stream << qint32(_trustedBots.size());
 | |
| 		for_const (auto botId, _trustedBots) {
 | |
| 			data.stream << quint64(botId);
 | |
| 		}
 | |
| 
 | |
| 		FileWriteDescriptor file(_trustedBotsKey);
 | |
| 		file.writeEncrypted(data);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void readTrustedBots() {
 | |
| 	if (!_trustedBotsKey) return;
 | |
| 
 | |
| 	FileReadDescriptor trusted;
 | |
| 	if (!readEncryptedFile(trusted, _trustedBotsKey)) {
 | |
| 		clearKey(_trustedBotsKey);
 | |
| 		_trustedBotsKey = 0;
 | |
| 		_writeMap();
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	qint32 size = 0;
 | |
| 	trusted.stream >> size;
 | |
| 	for (int i = 0; i < size; ++i) {
 | |
| 		quint64 botId = 0;
 | |
| 		trusted.stream >> botId;
 | |
| 		_trustedBots.insert(botId);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void makeBotTrusted(UserData *bot) {
 | |
| 	if (!isBotTrusted(bot)) {
 | |
| 		_trustedBots.insert(bot->id);
 | |
| 		writeTrustedBots();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool isBotTrusted(UserData *bot) {
 | |
| 	if (!_trustedBotsRead) {
 | |
| 		readTrustedBots();
 | |
| 		_trustedBotsRead = true;
 | |
| 	}
 | |
| 	return _trustedBots.contains(bot->id);
 | |
| }
 | |
| 
 | |
| bool encrypt(const void *src, void *dst, uint32 len, const void *key128) {
 | |
| 	if (!LocalKey) {
 | |
| 		return false;
 | |
| 	}
 | |
| 	MTP::aesEncryptLocal(src, dst, len, LocalKey, key128);
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| bool decrypt(const void *src, void *dst, uint32 len, const void *key128) {
 | |
| 	if (!LocalKey) {
 | |
| 		return false;
 | |
| 	}
 | |
| 	MTP::aesDecryptLocal(src, dst, len, LocalKey, key128);
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| struct ClearManagerData {
 | |
| 	QThread *thread;
 | |
| 	QMutex mutex;
 | |
| 	QList<int> tasks;
 | |
| 	bool working;
 | |
| };
 | |
| 
 | |
| ClearManager::ClearManager() : data(new ClearManagerData()) {
 | |
| 	data->thread = new QThread();
 | |
| 	data->working = true;
 | |
| }
 | |
| 
 | |
| bool ClearManager::addTask(int task) {
 | |
| 	QMutexLocker lock(&data->mutex);
 | |
| 	if (!data->working) return false;
 | |
| 
 | |
| 	if (!data->tasks.isEmpty() && (data->tasks.at(0) == ClearManagerAll)) return true;
 | |
| 	if (task == ClearManagerAll) {
 | |
| 		data->tasks.clear();
 | |
| 		if (!_draftsMap.isEmpty()) {
 | |
| 			_draftsMap.clear();
 | |
| 			_mapChanged = true;
 | |
| 		}
 | |
| 		if (!_draftCursorsMap.isEmpty()) {
 | |
| 			_draftCursorsMap.clear();
 | |
| 			_mapChanged = true;
 | |
| 		}
 | |
| 		if (_locationsKey) {
 | |
| 			_locationsKey = 0;
 | |
| 			_mapChanged = true;
 | |
| 		}
 | |
| 		if (_trustedBotsKey) {
 | |
| 			_trustedBotsKey = 0;
 | |
| 			_mapChanged = true;
 | |
| 		}
 | |
| 		if (_recentStickersKeyOld) {
 | |
| 			_recentStickersKeyOld = 0;
 | |
| 			_mapChanged = true;
 | |
| 		}
 | |
| 		if (_installedStickersKey || _featuredStickersKey || _recentStickersKey || _archivedStickersKey) {
 | |
| 			_installedStickersKey = _featuredStickersKey = _recentStickersKey = _archivedStickersKey = 0;
 | |
| 			_mapChanged = true;
 | |
| 		}
 | |
| 		if (_recentHashtagsAndBotsKey) {
 | |
| 			_recentHashtagsAndBotsKey = 0;
 | |
| 			_mapChanged = true;
 | |
| 		}
 | |
| 		_writeMap();
 | |
| 	} else {
 | |
| 		for (int32 i = 0, l = data->tasks.size(); i < l; ++i) {
 | |
| 			if (data->tasks.at(i) == task) return true;
 | |
| 		}
 | |
| 	}
 | |
| 	data->tasks.push_back(task);
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| bool ClearManager::hasTask(ClearManagerTask task) {
 | |
| 	QMutexLocker lock(&data->mutex);
 | |
| 	if (data->tasks.isEmpty()) return false;
 | |
| 	if (data->tasks.at(0) == ClearManagerAll) return true;
 | |
| 	for (int32 i = 0, l = data->tasks.size(); i < l; ++i) {
 | |
| 		if (data->tasks.at(i) == task) return true;
 | |
| 	}
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| void ClearManager::start() {
 | |
| 	moveToThread(data->thread);
 | |
| 	connect(data->thread, SIGNAL(started()), this, SLOT(onStart()));
 | |
| 	connect(data->thread, SIGNAL(finished()), data->thread, SLOT(deleteLater()));
 | |
| 	connect(data->thread, SIGNAL(finished()), this, SLOT(deleteLater()));
 | |
| 	data->thread->start();
 | |
| }
 | |
| 
 | |
| void ClearManager::stop() {
 | |
| 	{
 | |
| 		QMutexLocker lock(&data->mutex);
 | |
| 		data->tasks.clear();
 | |
| 	}
 | |
| 	auto thread = data->thread;
 | |
| 	thread->quit();
 | |
| 	thread->wait();
 | |
| }
 | |
| 
 | |
| ClearManager::~ClearManager() {
 | |
| 	delete data;
 | |
| }
 | |
| 
 | |
| void ClearManager::onStart() {
 | |
| 	while (true) {
 | |
| 		int task = 0;
 | |
| 		bool result = false;
 | |
| 		{
 | |
| 			QMutexLocker lock(&data->mutex);
 | |
| 			if (data->tasks.isEmpty()) {
 | |
| 				data->working = false;
 | |
| 				break;
 | |
| 			}
 | |
| 			task = data->tasks.at(0);
 | |
| 		}
 | |
| 		switch (task) {
 | |
| 		case ClearManagerAll: {
 | |
| 			result = QDir(cTempDir()).removeRecursively();
 | |
| 			QDirIterator di(_userBasePath, QDir::AllEntries | QDir::Hidden | QDir::System | QDir::NoDotAndDotDot);
 | |
| 			while (di.hasNext()) {
 | |
| 				di.next();
 | |
| 				const QFileInfo& fi = di.fileInfo();
 | |
| 				if (fi.isDir() && !fi.isSymLink()) {
 | |
| 					if (!QDir(di.filePath()).removeRecursively()) result = false;
 | |
| 				} else {
 | |
| 					QString path = di.filePath();
 | |
| 					if (!path.endsWith(qstr("map0")) && !path.endsWith(qstr("map1"))) {
 | |
| 						if (!QFile::remove(di.filePath())) result = false;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		} break;
 | |
| 		case ClearManagerDownloads:
 | |
| 			result = QDir(cTempDir()).removeRecursively();
 | |
| 		break;
 | |
| 		case ClearManagerStorage:
 | |
| 			result = true;
 | |
| 		break;
 | |
| 		}
 | |
| 		{
 | |
| 			QMutexLocker lock(&data->mutex);
 | |
| 			if (!data->tasks.isEmpty() && data->tasks.at(0) == task) {
 | |
| 				data->tasks.pop_front();
 | |
| 			}
 | |
| 			if (data->tasks.isEmpty()) {
 | |
| 				data->working = false;
 | |
| 			}
 | |
| 			if (result) {
 | |
| 				emit succeed(task, data->working ? 0 : this);
 | |
| 			} else {
 | |
| 				emit failed(task, data->working ? 0 : this);
 | |
| 			}
 | |
| 			if (!data->working) break;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| namespace internal {
 | |
| 
 | |
| Manager::Manager() {
 | |
| 	_mapWriteTimer.setSingleShot(true);
 | |
| 	connect(&_mapWriteTimer, SIGNAL(timeout()), this, SLOT(mapWriteTimeout()));
 | |
| 	_locationsWriteTimer.setSingleShot(true);
 | |
| 	connect(&_locationsWriteTimer, SIGNAL(timeout()), this, SLOT(locationsWriteTimeout()));
 | |
| }
 | |
| 
 | |
| void Manager::writeMap(bool fast) {
 | |
| 	if (!_mapWriteTimer.isActive() || fast) {
 | |
| 		_mapWriteTimer.start(fast ? 1 : kWriteMapTimeout);
 | |
| 	} else if (_mapWriteTimer.remainingTime() <= 0) {
 | |
| 		mapWriteTimeout();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void Manager::writingMap() {
 | |
| 	_mapWriteTimer.stop();
 | |
| }
 | |
| 
 | |
| void Manager::writeLocations(bool fast) {
 | |
| 	if (!_locationsWriteTimer.isActive() || fast) {
 | |
| 		_locationsWriteTimer.start(fast ? 1 : kWriteMapTimeout);
 | |
| 	} else if (_locationsWriteTimer.remainingTime() <= 0) {
 | |
| 		locationsWriteTimeout();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void Manager::writingLocations() {
 | |
| 	_locationsWriteTimer.stop();
 | |
| }
 | |
| 
 | |
| void Manager::mapWriteTimeout() {
 | |
| 	_writeMap(WriteMapWhen::Now);
 | |
| }
 | |
| 
 | |
| void Manager::locationsWriteTimeout() {
 | |
| 	_writeLocations(WriteMapWhen::Now);
 | |
| }
 | |
| 
 | |
| void Manager::finish() {
 | |
| 	if (_mapWriteTimer.isActive()) {
 | |
| 		mapWriteTimeout();
 | |
| 	}
 | |
| 	if (_locationsWriteTimer.isActive()) {
 | |
| 		locationsWriteTimeout();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| } // namespace internal
 | |
| } // namespace Local
 | 
