mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-26 00:55:16 -07:00
525cb7e1fd
- Remove subsync.defaultMode; subsync always opens manual picker - Default jellyfinRemoteSession warmup and nameMatchEnabled to false - Stop rewriting config file during legacy migration (resolve in-memory only) - Fix macOS quit on window-close for --setup launch mode
159 lines
4.7 KiB
TypeScript
159 lines
4.7 KiB
TypeScript
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import { ConfigValidationWarning, RawConfig, ResolvedConfig } from '../types/config';
|
|
import { DEFAULT_CONFIG, deepCloneConfig, deepMergeRawConfig } from './definitions';
|
|
import { ConfigPaths, loadRawConfig, loadRawConfigStrict } from './load';
|
|
import { resolveConfig } from './resolve';
|
|
import { applyLegacyAnkiConnectNPlusOneMigrationToContent } from './anki-connect-nplusone-migration';
|
|
import { applyLegacySubtitleStyleCssMigrationToContent } from './subtitle-style-css-migration';
|
|
|
|
export type ReloadConfigStrictResult =
|
|
| {
|
|
ok: true;
|
|
config: ResolvedConfig;
|
|
warnings: ConfigValidationWarning[];
|
|
path: string;
|
|
}
|
|
| {
|
|
ok: false;
|
|
error: string;
|
|
path: string;
|
|
};
|
|
|
|
export class ConfigStartupParseError extends Error {
|
|
readonly path: string;
|
|
readonly parseError: string;
|
|
|
|
constructor(configPath: string, parseError: string) {
|
|
super(
|
|
`Failed to parse startup config at ${configPath}: ${parseError}. Fix the config file and restart SubMiner.`,
|
|
);
|
|
this.name = 'ConfigStartupParseError';
|
|
this.path = configPath;
|
|
this.parseError = parseError;
|
|
}
|
|
}
|
|
|
|
export class ConfigService {
|
|
private readonly configPaths: ConfigPaths;
|
|
private rawConfig: RawConfig = {};
|
|
private resolvedConfig: ResolvedConfig = deepCloneConfig(DEFAULT_CONFIG);
|
|
private warnings: ConfigValidationWarning[] = [];
|
|
private configPathInUse!: string;
|
|
|
|
constructor(configDir: string) {
|
|
this.configPaths = {
|
|
configDir,
|
|
configFileJsonc: path.join(configDir, 'config.jsonc'),
|
|
configFileJson: path.join(configDir, 'config.json'),
|
|
};
|
|
const loadResult = loadRawConfigStrict(this.configPaths);
|
|
if (!loadResult.ok) {
|
|
throw new ConfigStartupParseError(loadResult.path, loadResult.error);
|
|
}
|
|
this.applyResolvedConfig(
|
|
this.migrateLegacyConfig(loadResult.config, loadResult.path),
|
|
loadResult.path,
|
|
);
|
|
}
|
|
|
|
getConfigPath(): string {
|
|
return this.configPathInUse;
|
|
}
|
|
|
|
getConfig(): ResolvedConfig {
|
|
return deepCloneConfig(this.resolvedConfig);
|
|
}
|
|
|
|
getRawConfig(): RawConfig {
|
|
return structuredClone(this.rawConfig);
|
|
}
|
|
|
|
getWarnings(): ConfigValidationWarning[] {
|
|
return [...this.warnings];
|
|
}
|
|
|
|
reloadConfig(): ResolvedConfig {
|
|
const { config, path: configPath } = loadRawConfig(this.configPaths);
|
|
return this.applyResolvedConfig(this.migrateLegacyConfig(config, configPath), configPath);
|
|
}
|
|
|
|
reloadConfigStrict(): ReloadConfigStrictResult {
|
|
const loadResult = loadRawConfigStrict(this.configPaths);
|
|
if (!loadResult.ok) {
|
|
return loadResult;
|
|
}
|
|
|
|
const { config, path: configPath } = loadResult;
|
|
const resolvedConfig = this.applyResolvedConfig(
|
|
this.migrateLegacyConfig(config, configPath),
|
|
configPath,
|
|
);
|
|
return {
|
|
ok: true,
|
|
config: resolvedConfig,
|
|
warnings: this.getWarnings(),
|
|
path: configPath,
|
|
};
|
|
}
|
|
|
|
saveRawConfig(config: RawConfig): void {
|
|
if (!fs.existsSync(this.configPaths.configDir)) {
|
|
fs.mkdirSync(this.configPaths.configDir, { recursive: true });
|
|
}
|
|
const targetPath = this.configPathInUse.endsWith('.json')
|
|
? this.configPathInUse
|
|
: this.configPaths.configFileJsonc;
|
|
fs.writeFileSync(targetPath, JSON.stringify(config, null, 2));
|
|
this.applyResolvedConfig(config, targetPath);
|
|
}
|
|
|
|
patchRawConfig(patch: RawConfig): void {
|
|
const merged = deepMergeRawConfig(this.getRawConfig(), patch);
|
|
this.saveRawConfig(merged);
|
|
}
|
|
|
|
private applyResolvedConfig(config: RawConfig, configPath: string): ResolvedConfig {
|
|
this.rawConfig = config;
|
|
this.configPathInUse = configPath;
|
|
const { resolved, warnings } = resolveConfig(config);
|
|
this.resolvedConfig = resolved;
|
|
this.warnings = warnings;
|
|
return this.getConfig();
|
|
}
|
|
|
|
private migrateLegacyConfig(config: RawConfig, configPath: string): RawConfig {
|
|
if (!fs.existsSync(configPath)) {
|
|
return config;
|
|
}
|
|
|
|
try {
|
|
let content = fs.readFileSync(configPath, 'utf-8');
|
|
let rawConfig = config;
|
|
let migrated = false;
|
|
for (const applyMigration of [
|
|
applyLegacyAnkiConnectNPlusOneMigrationToContent,
|
|
applyLegacySubtitleStyleCssMigrationToContent,
|
|
]) {
|
|
const migration = applyMigration({
|
|
content,
|
|
rawConfig,
|
|
});
|
|
if (!migration.migrated) {
|
|
continue;
|
|
}
|
|
content = migration.content;
|
|
rawConfig = migration.rawConfig;
|
|
migrated = true;
|
|
}
|
|
if (!migrated) {
|
|
return rawConfig;
|
|
}
|
|
return rawConfig;
|
|
} catch (error) {
|
|
console.error(`[ConfigService] legacy config migration failed for ${configPath}:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
}
|