From cb499a09d16f1f75c494ff4696cfd1403e783884 Mon Sep 17 00:00:00 2001 From: Ivan Vasilev Date: Thu, 3 Sep 2026 07:57:12 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A1=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D1=8F?= =?UTF-8?q?=D1=82=D1=8C=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=20=D1=81=D1=82?= =?UTF-8?q?=D1=80=D0=B0=D1=82=D0=B5=D0=B3=D0=B8=D0=B8=20=D0=B8=20=D0=B0?= =?UTF-8?q?=D0=B2=D1=82=D0=BE=D0=B7=D0=B0=D0=BF=D1=83=D1=81=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=BC=D0=B5=D0=B6=D0=B4=D1=83=20=D0=B7=D0=B0=D0=BF=D1=83=D1=81?= =?UTF-8?q?=D0=BA=D0=B0=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Настройки лежат в settings.json (userData) и восстанавливаются до открытия окна. Списки сайтов уже хранятся в userData/zapret-lists и отдельного сохранения не требуют. --- src/main/settings.ts | 73 +++++++++++++------------------------------- 1 file changed, 22 insertions(+), 51 deletions(-) diff --git a/src/main/settings.ts b/src/main/settings.ts index 73e4a3e..af133dd 100644 --- a/src/main/settings.ts +++ b/src/main/settings.ts @@ -1,23 +1,18 @@ import { randomUUID } from 'node:crypto' -import { readFile, rename, unlink, writeFile } from 'node:fs/promises' +import { readFile, rename, writeFile } from 'node:fs/promises' import { join } from 'node:path' import { app } from 'electron' /** - * Выбор пользователя, который должен пережить перезапуск приложения. Лежит в userData - * (`%AppData%\RKNboost\settings.json` на Windows — каталог по `productName` из - * electron-builder.yml) — то есть переживает и обновление, и переустановку приложения. + * Выбор пользователя между запусками: `settings.json` в userData (на Windows — + * `%AppData%\RKNboost`), поэтому переживает обновление и переустановку. * - * Списки сайтов сюда намеренно не входят: они и так хранятся рядом, в - * `userData/zapret-lists/*.txt` (см. `zapret/lists.ts`), каждый в своём файле — их читают - * прямо оттуда сами tpws и winws, и дублировать те же домены ещё и здесь означало бы - * держать два источника правды. + * Списки сайтов сюда не входят: они лежат рядом, в `userData/zapret-lists/*.txt`, и + * читаются прямо оттуда самими tpws и winws (см. `zapret/lists.ts`). * - * `null` в любом поле — «пользователь ничего не выбирал»: тогда решает движок (стратегия - * по умолчанию для платформы, автозапуск — как решил `Win32Engine`). + * `null` — «пользователь не выбирал», решает движок. */ export interface AppSettings { - /** Последняя выбранная стратегия обхода. */ strategyId: string | null /** Windows: запускать службу вместе с системой. На macOS не используется. */ autoStart: boolean | null @@ -25,35 +20,21 @@ export interface AppSettings { const DEFAULTS: AppSettings = { strategyId: null, autoStart: null } -/** - * Кэш в памяти нужен не для скорости, а чтобы писать только при реальном изменении: - * `rememberPreferences` дёргается из `ZapretEngine.patch()`, то есть на каждое изменение - * статуса, включая промежуточные `starting`/`stopping`. - */ let cache: AppSettings = { ...DEFAULTS } -/** - * До первой загрузки не пишем ничего: иначе движок, успевший обновить статус раньше - * `loadSettings()`, затёр бы файл значениями по умолчанию. - */ +/** До первой загрузки не пишем: иначе движок затёр бы файл умолчаниями. */ let loaded = false -/** Записи выстраиваем в очередь — параллельные rename в один файл друг друга обгоняют. */ +/** Очередь, чтобы два быстрых переключения не переставили rename местами. */ let writeQueue: Promise = Promise.resolve() function settingsPath(): string { return join(app.getPath('userData'), 'settings.json') } -/** - * Файл мог быть испорчен (аварийное завершение на записи) или остаться от версии с другим - * набором полей — читаем его как чужие данные, а не как свои: всё непонятное отбрасываем - * и продолжаем с умолчаниями, а не падаем на старте. - */ +/** Файл могли испортить или он остался от версии с другими полями — непонятное отбрасываем. */ function sanitize(raw: unknown): AppSettings { - if (typeof raw !== 'object' || raw === null) return { ...DEFAULTS } - const value = raw as Record - + const value = (typeof raw === 'object' && raw !== null ? raw : {}) as Record return { strategyId: typeof value.strategyId === 'string' && value.strategyId ? value.strategyId : null, autoStart: typeof value.autoStart === 'boolean' ? value.autoStart : null @@ -65,8 +46,7 @@ export async function loadSettings(): Promise { try { cache = sanitize(JSON.parse(await readFile(settingsPath(), 'utf8'))) } catch { - // Файла ещё нет (первый запуск) или он нечитаем — начинаем с умолчаний. - cache = { ...DEFAULTS } + cache = { ...DEFAULTS } // первый запуск или нечитаемый файл } loaded = true return { ...cache } @@ -76,30 +56,21 @@ export async function loadSettings(): Promise { async function writeSettings(settings: AppSettings): Promise { const path = settingsPath() const tmp = `${path}.${randomUUID()}.tmp` - try { - await writeFile(tmp, `${JSON.stringify(settings, null, 2)}\n`, 'utf8') - await rename(tmp, path) - } catch (error) { - // Не смогли сохранить выбор — это неприятно, но не повод ломать работающий обход. - console.warn('[settings] не удалось сохранить настройки:', error) - await unlink(tmp).catch(() => {}) - } + await writeFile(tmp, `${JSON.stringify(settings, null, 2)}\n`, 'utf8') + await rename(tmp, path) } /** - * Запоминает выбор пользователя. Вызывается из `ZapretEngine.patch()` на каждое изменение - * статуса, поэтому пишет на диск только когда что-то из сохраняемого действительно - * поменялось, и не ждёт записи: статус в интерфейсе не должен зависеть от файловой системы. + * Вызывается из `ZapretEngine.patch()`, то есть на каждое изменение статуса — отсюда + * сравнение с прошлым значением. Записи не ждём: интерфейс не должен зависеть от диска, + * а не сохранившийся выбор не повод ломать работающий обход. */ -export function rememberPreferences(preferences: Partial): void { +export function rememberPreferences(settings: AppSettings): void { if (!loaded) return + if (settings.strategyId === cache.strategyId && settings.autoStart === cache.autoStart) return - const next: AppSettings = { - strategyId: preferences.strategyId ?? cache.strategyId, - autoStart: preferences.autoStart ?? cache.autoStart - } - if (next.strategyId === cache.strategyId && next.autoStart === cache.autoStart) return - - cache = next - writeQueue = writeQueue.then(() => writeSettings(next)) + cache = settings + writeQueue = writeQueue + .then(() => writeSettings(settings)) + .catch((error: unknown) => console.warn('[settings] не удалось сохранить:', error)) }