tdesktop/Telegram/SourceFiles/storage/localstorage.cpp

1078 lines
28 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_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 <QtCore/QDirIterator>
#ifndef Q_OS_WIN
#include <unistd.h>
#endif // Q_OS_WIN
//extern "C" {
//#include <openssl/evp.h>
//} // 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<Data::DocumentMedia*> 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<Data::DocumentMedia*> 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<CountWaveformTask>(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<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, _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<Lang::Language> &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<Lang::Language> 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<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, _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<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();
} 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