Files
ZenEcho 489f3d4f1f feat: 新增游戏平台扫描、主题模式与重置功能
- 新增 BattleNet、WeGame、GOG 等游戏平台扫描支持
- 添加系统代理绕过列表和 Bootstrap DNS 配置
- 实现应用重置功能,支持清理数据并重启
- 增加主题模式选择(亮色/暗色/自动)
- 优化本地游戏扫描性能,支持并行扫描
- 改进代理监控,支持链式代理子进程检测
- 修复日志发送时的窗口销毁检查
- 更新图标和侧边栏布局
- 移除 prepare-singbox-core 脚本,简化构建流程
2026-02-22 03:57:55 +08:00

98 lines
2.6 KiB
TypeScript

import fs from 'fs-extra'
import path from 'node:path'
/**
* 本地 JSON 存储类配置选项
*/
export interface StoreOptions<T> {
/** 存储文件名称 (不含后缀) */
name: string
/** 默认数据 */
defaults: T
/** 存储目录路径 */
cwd: string
/** 保留的备份文件数量,默认为 5 */
backupCount?: number
}
/**
* 简单的本地 JSON 文件存储
*
* 提供数据的持久化存储、读取、自动备份功能。
*
* @template T - 存储数据的类型接口
*/
export class JsonStore<T> {
private filePath: string
private backupDir: string
private data: T
private options: StoreOptions<T>
constructor(options: StoreOptions<T>) {
this.options = { backupCount: 5, ...options }
this.filePath = path.join(this.options.cwd, `${this.options.name}.json`)
this.backupDir = path.join(this.options.cwd, 'backups', this.options.name)
this.data = this.options.defaults
this.init()
}
private init() {
try {
if (fs.existsSync(this.filePath)) {
this.data = fs.readJsonSync(this.filePath)
} else {
this.save()
}
} catch (e) {
console.error(`加载存储 ${this.options.name} 失败:`, e)
// Attempt to load latest backup if main file is corrupted?
}
}
public get(): T {
return this.data
}
public set(data: T) {
this.data = data
this.save()
}
private async save() {
try {
await this.backup()
await fs.writeJson(this.filePath, this.data, { spaces: 2 })
} catch (e) {
console.error(`保存存储 ${this.options.name} 失败:`, e)
}
}
private async backup() {
if (!this.options.backupCount || this.options.backupCount <= 0) return
try {
await fs.ensureDir(this.backupDir)
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
const backupPath = path.join(this.backupDir, `${this.options.name}-${timestamp}.json`)
if (fs.existsSync(this.filePath)) {
await fs.copy(this.filePath, backupPath)
}
// Cleanup old backups
const files = await fs.readdir(this.backupDir)
const backups = files
.filter(f => f.startsWith(this.options.name) && f.endsWith('.json'))
.map(f => path.join(this.backupDir, f))
.sort((a, b) => fs.statSync(b).mtime.getTime() - fs.statSync(a).mtime.getTime())
if (backups.length > this.options.backupCount) {
const toDelete = backups.slice(this.options.backupCount)
await Promise.all(toDelete.map(f => fs.remove(f)))
}
} catch (e) {
console.error(`备份 ${this.options.name} 失败:`, e)
}
}
}