2026-08-17 00:43:33 +03:00
|
|
|
#include "core/SettingsStore.h"
|
|
|
|
|
|
2026-09-01 19:25:07 +03:00
|
|
|
#include <QDir>
|
2026-08-17 00:43:33 +03:00
|
|
|
#include <QSettings>
|
2026-09-01 19:25:07 +03:00
|
|
|
#include <QUrl>
|
|
|
|
|
#include <algorithm>
|
2026-08-17 00:43:33 +03:00
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
constexpr auto kGroupVaults = "vaults";
|
|
|
|
|
constexpr auto kKeyVaults = "vaults/vaultList";
|
|
|
|
|
constexpr auto kKeyCurrent = "vaults/current";
|
|
|
|
|
constexpr auto kKeyProviders = "sync/providers";
|
2026-09-01 19:25:07 +03:00
|
|
|
constexpr auto kKeyTheme = "ui/theme";
|
|
|
|
|
constexpr auto kKeyLeftW = "ui/leftPanelWidth";
|
|
|
|
|
constexpr auto kKeyRightW = "ui/rightPanelWidth";
|
|
|
|
|
constexpr auto kKeyViewModes = "ui/viewModes";
|
|
|
|
|
|
|
|
|
|
QString encodeKey(const QString &s) {
|
|
|
|
|
// Процент-кодирование чтобы избежать интерпретации "/" как разделителя групп в QSettings Ini
|
|
|
|
|
return QString::fromLatin1(QUrl::toPercentEncoding(s, QByteArray(), QByteArray("/")));
|
|
|
|
|
}
|
|
|
|
|
QString encodeRel(const QString &rel) {
|
|
|
|
|
// Для относительных путей тоже кодируем "/" как %2F чтобы ключ остался плоским
|
|
|
|
|
return QString::fromLatin1(QUrl::toPercentEncoding(rel));
|
|
|
|
|
}
|
|
|
|
|
QString decodeKey(const QString &s) {
|
|
|
|
|
return QUrl::fromPercentEncoding(s.toLatin1());
|
|
|
|
|
}
|
2026-08-17 00:43:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SettingsStore::SettingsStore(QObject *parent)
|
|
|
|
|
: QObject(parent)
|
|
|
|
|
{
|
|
|
|
|
QSettings s;
|
|
|
|
|
m_providers = s.value(kKeyProviders).toList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QStringList SettingsStore::vaults() const
|
|
|
|
|
{
|
|
|
|
|
QSettings s;
|
|
|
|
|
return s.value(kKeyVaults).toStringList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString SettingsStore::currentVault() const
|
|
|
|
|
{
|
|
|
|
|
QSettings s;
|
|
|
|
|
return s.value(kKeyCurrent).toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SettingsStore::setCurrentVault(const QString &path)
|
|
|
|
|
{
|
|
|
|
|
QSettings s;
|
2026-09-01 19:25:07 +03:00
|
|
|
s.setValue(kKeyCurrent, QDir::cleanPath(path));
|
|
|
|
|
s.sync();
|
2026-08-17 00:43:33 +03:00
|
|
|
emit currentVaultChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QVariantList SettingsStore::providers() const
|
|
|
|
|
{
|
|
|
|
|
return m_providers;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SettingsStore::addVault(const QString &path)
|
|
|
|
|
{
|
2026-09-01 19:25:07 +03:00
|
|
|
const QString clean = QDir::cleanPath(path);
|
|
|
|
|
if (clean.isEmpty())
|
|
|
|
|
return;
|
2026-08-17 00:43:33 +03:00
|
|
|
QStringList list = vaults();
|
2026-09-01 19:25:07 +03:00
|
|
|
// нормализуем существующие записи
|
|
|
|
|
for (QString &v : list) v = QDir::cleanPath(v);
|
|
|
|
|
list.removeAll(clean);
|
|
|
|
|
list.prepend(clean);
|
2026-08-17 00:43:33 +03:00
|
|
|
QSettings s;
|
|
|
|
|
s.setValue(kKeyVaults, list);
|
2026-09-01 19:25:07 +03:00
|
|
|
s.setValue(kKeyCurrent, clean);
|
|
|
|
|
s.sync();
|
2026-08-17 00:43:33 +03:00
|
|
|
emit vaultsChanged();
|
|
|
|
|
emit currentVaultChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SettingsStore::addProvider(const QString &provider, const QVariantMap &config)
|
|
|
|
|
{
|
2026-09-01 19:25:07 +03:00
|
|
|
if (provider.isEmpty())
|
|
|
|
|
return;
|
2026-08-17 00:43:33 +03:00
|
|
|
QVariantList list = m_providers;
|
|
|
|
|
for (auto &item : list) {
|
|
|
|
|
auto m = item.toMap();
|
|
|
|
|
if (m.value("provider") == provider) {
|
|
|
|
|
m = config;
|
|
|
|
|
m["provider"] = provider;
|
|
|
|
|
item = m;
|
|
|
|
|
m_providers = list;
|
|
|
|
|
goto save;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
auto m = config;
|
|
|
|
|
m["provider"] = provider;
|
|
|
|
|
list.append(m);
|
|
|
|
|
m_providers = list;
|
|
|
|
|
}
|
|
|
|
|
save:
|
|
|
|
|
QSettings s;
|
|
|
|
|
s.setValue(kKeyProviders, m_providers);
|
2026-09-01 19:25:07 +03:00
|
|
|
s.sync();
|
2026-08-17 00:43:33 +03:00
|
|
|
emit providersChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SettingsStore::hasVault(const QString &path) const
|
|
|
|
|
{
|
2026-09-01 19:25:07 +03:00
|
|
|
const QString clean = QDir::cleanPath(path);
|
|
|
|
|
const QStringList list = vaults();
|
|
|
|
|
for (const QString &v : list)
|
|
|
|
|
if (QDir::cleanPath(v) == clean)
|
|
|
|
|
return true;
|
|
|
|
|
return false;
|
2026-08-17 00:43:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SettingsStore::clearProvider(const QString &provider)
|
|
|
|
|
{
|
|
|
|
|
QVariantList list = m_providers;
|
|
|
|
|
list.erase(std::remove_if(list.begin(), list.end(),
|
|
|
|
|
[&provider](const QVariant &v) {
|
|
|
|
|
return v.toMap().value("provider") == provider;
|
|
|
|
|
}),
|
|
|
|
|
list.end());
|
|
|
|
|
m_providers = list;
|
|
|
|
|
QSettings s;
|
|
|
|
|
s.setValue(kKeyProviders, m_providers);
|
2026-09-01 19:25:07 +03:00
|
|
|
s.sync();
|
2026-08-17 00:43:33 +03:00
|
|
|
emit providersChanged();
|
2026-09-01 19:25:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString SettingsStore::theme() const
|
|
|
|
|
{
|
|
|
|
|
QSettings s;
|
|
|
|
|
const QString t = s.value(kKeyTheme, QStringLiteral("light")).toString();
|
|
|
|
|
return (t == "dark" || t == "light") ? t : QStringLiteral("light");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SettingsStore::setTheme(const QString &t)
|
|
|
|
|
{
|
|
|
|
|
const QString clean = (t == "dark") ? QStringLiteral("dark") : QStringLiteral("light");
|
|
|
|
|
QSettings s;
|
|
|
|
|
if (s.value(kKeyTheme).toString() == clean)
|
|
|
|
|
return;
|
|
|
|
|
s.setValue(kKeyTheme, clean);
|
|
|
|
|
s.sync();
|
|
|
|
|
emit themeChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int SettingsStore::leftPanelWidth() const
|
|
|
|
|
{
|
|
|
|
|
QSettings s;
|
|
|
|
|
return s.value(kKeyLeftW, 220).toInt();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SettingsStore::setLeftPanelWidth(int w)
|
|
|
|
|
{
|
|
|
|
|
if (w < 100) w = 100;
|
|
|
|
|
if (w > 800) w = 800;
|
|
|
|
|
QSettings s;
|
|
|
|
|
if (s.value(kKeyLeftW).toInt() == w)
|
|
|
|
|
return;
|
|
|
|
|
s.setValue(kKeyLeftW, w);
|
|
|
|
|
s.sync();
|
|
|
|
|
emit leftPanelWidthChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int SettingsStore::rightPanelWidth() const
|
|
|
|
|
{
|
|
|
|
|
QSettings s;
|
|
|
|
|
return s.value(kKeyRightW, 220).toInt();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SettingsStore::setRightPanelWidth(int w)
|
|
|
|
|
{
|
|
|
|
|
if (w < 100) w = 100;
|
|
|
|
|
if (w > 800) w = 800;
|
|
|
|
|
QSettings s;
|
|
|
|
|
if (s.value(kKeyRightW).toInt() == w)
|
|
|
|
|
return;
|
|
|
|
|
s.setValue(kKeyRightW, w);
|
|
|
|
|
s.sync();
|
|
|
|
|
emit rightPanelWidthChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString SettingsStore::viewModeForFile(const QString &vault, const QString &filePath) const
|
|
|
|
|
{
|
|
|
|
|
if (vault.isEmpty() || filePath.isEmpty())
|
|
|
|
|
return QStringLiteral("edit");
|
|
|
|
|
QSettings s;
|
|
|
|
|
const QVariantMap root = s.value(kKeyViewModes).toMap();
|
|
|
|
|
const QString cleanVault = QDir::cleanPath(vault);
|
|
|
|
|
const QString cleanPath = QDir::cleanPath(filePath);
|
|
|
|
|
QString rel = cleanPath;
|
|
|
|
|
if (cleanPath.startsWith(cleanVault)) {
|
|
|
|
|
rel = cleanPath.mid(cleanVault.length());
|
|
|
|
|
while (rel.startsWith("/") || rel.startsWith("\\")) rel.remove(0,1);
|
|
|
|
|
} else {
|
|
|
|
|
rel = cleanPath;
|
|
|
|
|
}
|
|
|
|
|
const QString oldKey = cleanVault + "|" + rel;
|
|
|
|
|
if (root.contains(oldKey)) {
|
|
|
|
|
const QString m = root.value(oldKey).toString();
|
|
|
|
|
if (m == "preview" || m == "live" || m == "edit") return m;
|
|
|
|
|
}
|
|
|
|
|
const QString encVault = encodeKey(cleanVault);
|
|
|
|
|
const QString encRel = encodeRel(rel);
|
|
|
|
|
const QString encKey = encVault + "|" + encRel;
|
|
|
|
|
if (root.contains(encKey)) {
|
|
|
|
|
const QString m = root.value(encKey).toString();
|
|
|
|
|
if (m == "preview" || m == "live" || m == "edit") return m;
|
|
|
|
|
}
|
|
|
|
|
// Обратная совместимость: старый вложенный формат
|
|
|
|
|
QVariantMap vaultMap = root.value(encVault).toMap();
|
|
|
|
|
if (vaultMap.isEmpty())
|
|
|
|
|
vaultMap = root.value(cleanVault).toMap();
|
|
|
|
|
QString mode = vaultMap.value(encRel).toString();
|
|
|
|
|
if (mode.isEmpty())
|
|
|
|
|
mode = vaultMap.value(rel).toString();
|
|
|
|
|
if (mode == "preview" || mode == "live" || mode == "edit")
|
|
|
|
|
return mode;
|
|
|
|
|
return QStringLiteral("edit");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SettingsStore::setViewModeForFile(const QString &vault, const QString &filePath, const QString &mode)
|
|
|
|
|
{
|
|
|
|
|
if (vault.isEmpty() || filePath.isEmpty())
|
|
|
|
|
return;
|
|
|
|
|
QString cleanMode = mode;
|
|
|
|
|
if (cleanMode != "preview" && cleanMode != "live")
|
|
|
|
|
cleanMode = "edit";
|
|
|
|
|
QSettings s;
|
|
|
|
|
QVariantMap root = s.value(kKeyViewModes).toMap();
|
|
|
|
|
const QString cleanVault = QDir::cleanPath(vault);
|
|
|
|
|
const QString cleanPath = QDir::cleanPath(filePath);
|
|
|
|
|
QString rel = cleanPath;
|
|
|
|
|
if (cleanPath.startsWith(cleanVault)) {
|
|
|
|
|
rel = cleanPath.mid(cleanVault.length());
|
|
|
|
|
while (rel.startsWith("/") || rel.startsWith("\\")) rel.remove(0,1);
|
|
|
|
|
} else {
|
|
|
|
|
rel = cleanPath;
|
|
|
|
|
}
|
|
|
|
|
const QString encVault = encodeKey(cleanVault);
|
|
|
|
|
const QString encRel = encodeRel(rel);
|
|
|
|
|
const QString encKey = encVault + "|" + encRel;
|
|
|
|
|
// мигрируем старые ключи
|
|
|
|
|
const QString oldKey = cleanVault + "|" + rel;
|
|
|
|
|
if (root.contains(oldKey))
|
|
|
|
|
root.remove(oldKey);
|
|
|
|
|
// мигрируем старый вложенный формат если есть
|
|
|
|
|
if (root.contains(encVault) && root.value(encVault).typeId() == QMetaType::QVariantMap) {
|
|
|
|
|
QVariantMap vm = root.value(encVault).toMap();
|
|
|
|
|
if (!vm.isEmpty()) {
|
|
|
|
|
// переносим все вложенные записи в плоский формат
|
|
|
|
|
for (auto it = vm.constBegin(); it != vm.constEnd(); ++it) {
|
|
|
|
|
const QString innerKey = encVault + "|" + it.key();
|
|
|
|
|
if (!root.contains(innerKey))
|
|
|
|
|
root.insert(innerKey, it.value());
|
|
|
|
|
}
|
|
|
|
|
root.remove(encVault);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (root.contains(cleanVault) && root.value(cleanVault).typeId() == QMetaType::QVariantMap) {
|
|
|
|
|
QVariantMap vm = root.value(cleanVault).toMap();
|
|
|
|
|
root.remove(cleanVault);
|
|
|
|
|
for (auto it = vm.constBegin(); it != vm.constEnd(); ++it) {
|
|
|
|
|
const QString k = encodeKey(cleanVault) + "|" + encodeRel(it.key());
|
|
|
|
|
if (!root.contains(k))
|
|
|
|
|
root.insert(k, it.value());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (root.value(encKey).toString() == cleanMode)
|
|
|
|
|
return;
|
|
|
|
|
root.insert(encKey, cleanMode);
|
|
|
|
|
s.setValue(kKeyViewModes, root);
|
|
|
|
|
s.sync();
|
2026-08-17 00:43:33 +03:00
|
|
|
}
|