You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

230 lines
6.9 KiB

import fs from "fs-extra"
import { app } from "electron"
import path from "path"
import { cloneDeep } from "lodash"
import Config from "config"
import type { IDefaultConfig } from "config"
import _debug from "debug"
const debug = _debug("app:setting")
type IConfig = IDefaultConfig
type IOnFunc = (n: IConfig, c: IConfig, keys?: (keyof IConfig)[]) => void
type IT = (keyof IConfig)[] | keyof IConfig | "_"
let storagePath = path.join(app.getPath("documents"), Config.app_title)
const storagePathDev = path.join(app.getPath("documents"), Config.app_title + "-dev")
if (process.env.NODE_ENV === "development") {
storagePath = storagePathDev
}
const _tempConfig = cloneDeep(Config.default_config as IConfig)
Object.keys(_tempConfig).forEach(key => {
if (typeof _tempConfig[key] === "string" && _tempConfig[key].includes("$storagePath$")) {
_tempConfig[key] = _tempConfig[key].replace(/\$storagePath\$/g, storagePath)
if (_tempConfig[key] && path.isAbsolute(_tempConfig[key])) {
_tempConfig[key] = path.normalize(_tempConfig[key])
}
}
})
function isPath(str) {
// 使用正则表达式检查字符串是否以斜杠或盘符开头
return /^(?:\/|[a-zA-Z]:\\)/.test(str)
}
function init(config: IConfig) {
// 在配置初始化后执行
Object.keys(config).forEach(key => {
if (config[key] && isPath(config[key]) && path.isAbsolute(config[key])) {
fs.ensureDirSync(config[key])
}
})
// 在配置初始化后执行
// fs.ensureDirSync(config["snippet.storagePath"])
// fs.ensureDirSync(config["bookmark.storagePath"])
}
// 判断是否是空文件夹
function isEmptyDir(fPath: string) {
const pa = fs.readdirSync(fPath)
if (pa.length === 0) {
return true
} else {
return false
}
}
class SettingClass {
constructor() {
debug(`Setting inited`)
this.init()
}
#cb: [IT, IOnFunc][] = []
onChange(fn: IOnFunc, that?: any)
onChange(key: IT, fn: IOnFunc, that?: any)
onChange(fnOrType: IT | IOnFunc, fnOrThat: IOnFunc | any = null, that: any = null) {
if (typeof fnOrType === "function") {
this.#cb.push(["_", fnOrType.bind(fnOrThat)])
} else {
this.#cb.push([fnOrType, fnOrThat.bind(that)])
}
}
#runCB(n: IConfig, c: IConfig, keys: (keyof IConfig)[]) {
for (let i = 0; i < this.#cb.length; i++) {
const temp = this.#cb[i]
const k = temp[0]
const fn = temp[1]
if (k === "_") {
fn(n, c, keys)
}
if (typeof k === "string" && keys.includes(k as keyof IConfig)) {
fn(n, c)
}
if (Array.isArray(k) && k.filter(v => keys.indexOf(v) !== -1).length) {
fn(n, c)
}
}
}
#pathFile: string =
process.env.NODE_ENV === "development"
? path.resolve(app.getPath("userData"), "./config_path-dev")
: path.resolve(app.getPath("userData"), "./config_path")
#config: IConfig = cloneDeep(_tempConfig)
#configPath(storagePath?: string): string {
return path.join(storagePath || this.#config.storagePath, "./config.json")
}
/**
* 读取配置文件变量同步
* @param confingPath 配置文件路径
*/
#syncVar(confingPath?: string) {
const configFile = this.#configPath(confingPath)
if (!fs.pathExistsSync(configFile)) {
fs.ensureFileSync(configFile)
fs.writeJSONSync(configFile, {})
}
const config = fs.readJSONSync(configFile) as IConfig
confingPath && (config.storagePath = confingPath)
// 优先取本地的值
for (const key in config) {
// if (Object.prototype.hasOwnProperty.call(this.#config, key)) {
// this.#config[key] = config[key] || this.#config[key]
// }
// 删除配置时本地的配置不会改变,想一下哪种方式更好
this.#config[key] = config[key] ?? this.#config[key]
}
}
init() {
debug(`位置:${this.#pathFile}`)
if (fs.pathExistsSync(this.#pathFile)) {
const confingPath = fs.readFileSync(this.#pathFile, { encoding: "utf8" })
if (confingPath && fs.pathExistsSync(this.#configPath(confingPath))) {
this.#syncVar(confingPath)
// 防止增加了配置本地却没变的情况
this.#sync(confingPath)
} else {
this.#syncVar(confingPath)
this.#sync(confingPath)
}
} else {
this.#syncVar()
this.#sync()
}
init.call(this, this.#config)
}
config() {
return this.#config
}
#sync(c?: string) {
const config = cloneDeep(this.#config)
delete config.storagePath
const p = this.#configPath(c)
fs.ensureFileSync(p)
fs.writeJSONSync(this.#configPath(c), config)
}
#change(p: string) {
const storagePath = this.#config.storagePath
if (fs.existsSync(storagePath) && !fs.existsSync(p)) {
fs.moveSync(storagePath, p)
}
if (fs.existsSync(p) && fs.existsSync(storagePath) && isEmptyDir(p)) {
fs.moveSync(storagePath, p, { overwrite: true })
}
fs.writeFileSync(this.#pathFile, p, { encoding: "utf8" })
}
reset(key: keyof IConfig) {
this.set(key, cloneDeep(_tempConfig[key]))
}
set(key: keyof IConfig | Partial<IConfig>, value?: any) {
const oldMainConfig = Object.assign({}, this.#config)
let isChange = false
const changeKeys: (keyof IConfig)[] = []
const canChangeStorage = (targetPath: string) => {
if (fs.existsSync(oldMainConfig.storagePath) && fs.existsSync(targetPath) && !isEmptyDir(targetPath)) {
if (fs.existsSync(path.join(targetPath, "./config.json"))) {
return true
}
return false
}
return true
}
if (typeof key === "string") {
if (value != undefined && value !== this.#config[key]) {
if (key === "storagePath") {
if (!canChangeStorage(value)) {
throw "无法改变存储地址"
return
}
this.#change(value)
changeKeys.push("storagePath")
this.#config["storagePath"] = value
} else {
changeKeys.push(key)
this.#config[key as string] = value
}
isChange = true
}
} else {
if (key["storagePath"] !== undefined && key["storagePath"] !== this.#config["storagePath"]) {
if (!canChangeStorage(key["storagePath"])) {
throw "无法改变存储地址"
return
}
this.#change(key["storagePath"])
this.#config["storagePath"] = key["storagePath"]
changeKeys.push("storagePath")
isChange = true
}
for (const _ in key) {
if (Object.prototype.hasOwnProperty.call(key, _)) {
const v = key[_]
if (v != undefined && _ !== "storagePath" && v !== this.#config[_]) {
this.#config[_] = v
changeKeys.push(_ as keyof IConfig)
isChange = true
}
}
}
}
if (isChange) {
this.#sync()
this.#runCB(this.#config, oldMainConfig, changeKeys)
}
}
values<T extends keyof IConfig>(key: T): IConfig[T] {
return this.#config[key]
}
}
const Setting = new SettingClass()
export default Setting
export { Setting }