/* 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_common.h" #include "storage/storage_account.h" #include "storage/details/storage_file_utilities.h" #include "storage/details/storage_settings_scheme.h" #include "data/data_session.h" #include "data/data_document.h" #include "data/data_document_media.h" #include "base/platform/base_platform_info.h" #include "ui/effects/animation_value.h" #include "core/update_checker.h" #include "media/audio/media_audio.h" #include "mtproto/dc_options.h" #include "core/application.h" #include "main/main_session.h" #include "window/themes/window_theme.h" #include "facades.h" #include #ifndef Q_OS_WIN #include #endif // Q_OS_WIN //extern "C" { //#include //} // extern "C" namespace Local { namespace { constexpr auto kThemeFileSizeLimit = 5 * 1024 * 1024; constexpr auto kFileLoaderQueueStopTimeout = crl::time(5000); const auto kThemeNewPathRelativeTag = qstr("special://new_tag"); using namespace Storage::details; using Storage::FileKey; using Database = Storage::Cache::Database; QString _basePath, _userBasePath, _userDbPath; bool _started = false; TaskQueue *_localLoader = nullptr; bool _working() { return !_basePath.isEmpty(); } bool CheckStreamStatus(QDataStream &stream) { if (stream.status() != QDataStream::Ok) { LOG(("Bad data stream status: %1").arg(stream.status())); return false; } return true; } QByteArray _settingsSalt; auto OldKey = MTP::AuthKeyPtr(); auto SettingsKey = MTP::AuthKeyPtr(); 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; FileKey _langPackKey = 0; FileKey _languagesKey = 0; int32 _oldSettingsVersion = 0; enum class WriteMapWhen { Now, Fast, Soon, }; void applyReadContext(ReadSettingsContext &&context) { Core::App().dcOptions()->addFromOther(std::move(context.dcOptions)); _themeKeyLegacy = context.themeKeyLegacy; _themeKeyDay = context.themeKeyDay; _themeKeyNight = context.themeKeyNight; _langPackKey = context.langPackKey; _languagesKey = context.languagesKey; } 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; } OldKey = CreateLegacyLocalKey(QByteArray(), salt); 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; const auto testPostfix = (cTestMode() ? qsl("_test") : QString()); QFile file(cWorkingDir() + cDataFile() + testPostfix); 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; } } // namespace void finish() { delete base::take(_localLoader); } void InitialLoadTheme(); bool ApplyDefaultNightMode(); void readLangPack(); void start() { Expects(_basePath.isEmpty()); _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"), _basePath)) { _readOldSettings(true, context); _readOldUserSettings(false, context); // needed further in _readUserSettings _readOldMtpData(false, context); // needed further in _readMtpData applyReadContext(std::move(context)); if (!ApplyDefaultNightMode()) { writeSettings(); } return; } 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(); } SettingsKey = CreateLegacyLocalKey(QByteArray(), salt); 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; applyReadContext(std::move(context)); InitialLoadTheme(); readLangPack(); } 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"), _basePath); if (_settingsSalt.isEmpty() || !SettingsKey) { _settingsSalt.resize(LocalEncryptSaltSize); memset_rand(_settingsSalt.data(), _settingsSalt.size()); SettingsKey = CreateLegacyLocalKey(QByteArray(), _settingsSalt); } 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(Global::TxtDomainString()); size += sizeof(quint32) + Serialize::stringSize(cDialogLastPath()); 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(dbiTxtDomainString) << Global::TxtDomainString(); data.stream << quint32(dbiDialogLastPath) << cDialogLastPath(); 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); } 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(); } Window::Theme::Background()->reset(); _oldSettingsVersion = 0; } int32 oldSettingsVersion() { return _oldSettingsVersion; } class CountWaveformTask : public Task { public: CountWaveformTask(not_null media) : _doc(media->owner()) , _loc(_doc->location(true)) , _data(media->bytes()) , _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; } _doc->owner().requestDocumentViewRepaint(_doc); } } ~CountWaveformTask() { if (_data.isEmpty() && _doc) { _loc.accessDisable(); } } protected: DocumentData *_doc = nullptr; FileLocation _loc; QByteArray _data; VoiceWaveform _waveform; char _wavemax; }; void countVoiceWaveform(not_null media) { const auto document = media->owner(); 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(media)); memcpy(voice->waveform.data() + 1, &taskId, sizeof(taskId)); } } } void cancelTask(TaskId id) { if (_localLoader) { _localLoader->cancelTask(id); } } Window::Theme::Saved readThemeUsingKey(FileKey key) { using namespace Window::Theme; FileReadDescriptor theme; if (!ReadEncryptedFile(theme, key, _basePath, 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 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, _basePath); themeKey = 0; writeSettings(); } return; } if (!themeKey) { themeKey = GenerateKey(_basePath); 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, _basePath); 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(); } } bool ApplyDefaultNightMode() { const auto NightByDefault = Platform::IsMacStoreBuild(); if (!NightByDefault || Window::Theme::IsNightMode() || _themeKeyDay || _themeKeyNight || _themeKeyLegacy) { return false; } Window::Theme::ToggleNightMode(); Window::Theme::KeepApplied(); return true; } Window::Theme::Saved readThemeAfterSwitch() { const auto key = Window::Theme::IsNightMode() ? _themeKeyNight : _themeKeyDay; return readThemeUsingKey(key); } void readLangPack() { FileReadDescriptor langpack; if (!_langPackKey || !ReadEncryptedFile(langpack, _langPackKey, _basePath, 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 = GenerateKey(_basePath); writeSettings(); } EncryptedDescriptor data(Serialize::bytearraySize(langpack)); data.stream << langpack; FileWriteDescriptor file(_langPackKey, _basePath); file.writeEncrypted(data, SettingsKey); } void saveRecentLanguages(const std::vector &list) { if (list.empty()) { if (_languagesKey) { ClearKey(_languagesKey, _basePath); _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 = GenerateKey(_basePath); 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, _basePath); 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 readRecentLanguages() { FileReadDescriptor languages; if (!_languagesKey || !ReadEncryptedFile(languages, _languagesKey, _basePath, SettingsKey)) { return {}; } qint32 count = 0; languages.stream >> count; if (count <= 0) { return {}; } auto result = std::vector(); 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, _basePath, 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 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)); } } } bool readOldMtpData(bool remove, ReadSettingsContext &context) { return _readOldMtpData(remove, context); } bool readOldUserSettings(bool remove, ReadSettingsContext &context) { return _readOldUserSettings(remove, context); } struct ClearManagerData { QThread *thread; QMutex mutex; QList 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(); } 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(); } break; case ClearManagerDownloads: result = QDir(cTempDir()).removeRecursively(); 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 Local