Сохранять выбор стратегии и автозапуска между запусками
Настройки лежат в settings.json (userData) и восстанавливаются до открытия окна. Списки сайтов уже хранятся в userData/zapret-lists и отдельного сохранения не требуют.
This commit is contained in:
+22
-51
@@ -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<void> = 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<string, unknown>
|
||||
|
||||
const value = (typeof raw === 'object' && raw !== null ? raw : {}) as Record<string, unknown>
|
||||
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<AppSettings> {
|
||||
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<AppSettings> {
|
||||
async function writeSettings(settings: AppSettings): Promise<void> {
|
||||
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<AppSettings>): 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))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user