95 lines
2.5 KiB
JavaScript
95 lines
2.5 KiB
JavaScript
|
|
// codex_data_dir.js —— 会话相关功能使用的 Codex 数据目录,可由用户覆盖默认 ~/.codex。
|
|||
|
|
const { app } = require("electron");
|
|||
|
|
const fs = require("fs");
|
|||
|
|
const os = require("os");
|
|||
|
|
const path = require("path");
|
|||
|
|
|
|||
|
|
const DEFAULT_DIR = path.join(os.homedir(), ".codex");
|
|||
|
|
let loaded = false;
|
|||
|
|
let configuredDir = null;
|
|||
|
|
|
|||
|
|
function configFile() {
|
|||
|
|
return path.join(app.getPath("userData"), "codex-data-dir.json");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function normalize(dir) {
|
|||
|
|
if (typeof dir !== "string" || !dir.trim() || !path.isAbsolute(dir.trim())) return null;
|
|||
|
|
return path.resolve(dir.trim());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function load() {
|
|||
|
|
if (loaded) return;
|
|||
|
|
loaded = true;
|
|||
|
|
try {
|
|||
|
|
configuredDir = normalize(JSON.parse(fs.readFileSync(configFile(), "utf-8")).path);
|
|||
|
|
} catch {}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function get() {
|
|||
|
|
load();
|
|||
|
|
return configuredDir || DEFAULT_DIR;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function isCodexDataDir(dir) {
|
|||
|
|
try {
|
|||
|
|
const names = fs.readdirSync(dir);
|
|||
|
|
return names.includes("sessions") || names.includes("archived_sessions") || names.includes("config.toml") ||
|
|||
|
|
names.includes("auth.json") || names.some((name) => /^state_.*\.sqlite(?:-(?:wal|shm))?$/.test(name));
|
|||
|
|
} catch {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function isDirectory(dir) {
|
|||
|
|
try {
|
|||
|
|
return fs.statSync(dir).isDirectory();
|
|||
|
|
} catch {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function writeAtomic(file, text) {
|
|||
|
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|||
|
|
const tmp = file + ".tmp-" + process.pid + "-" + Date.now();
|
|||
|
|
const fd = fs.openSync(tmp, "w", 0o600);
|
|||
|
|
try {
|
|||
|
|
fs.writeFileSync(fd, text, "utf-8");
|
|||
|
|
fs.fsyncSync(fd);
|
|||
|
|
} finally {
|
|||
|
|
fs.closeSync(fd);
|
|||
|
|
}
|
|||
|
|
try {
|
|||
|
|
fs.renameSync(tmp, file);
|
|||
|
|
} finally {
|
|||
|
|
try { fs.rmSync(tmp, { force: true }); } catch {}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function set(dir) {
|
|||
|
|
const next = normalize(dir);
|
|||
|
|
if (!next || !isDirectory(next)) {
|
|||
|
|
throw new Error("请选择存在的 Codex 数据目录");
|
|||
|
|
}
|
|||
|
|
if (!isCodexDataDir(next)) {
|
|||
|
|
throw new Error("所选目录未找到 Codex 数据(sessions、config.toml 或 state_*.sqlite)");
|
|||
|
|
}
|
|||
|
|
configuredDir = next === DEFAULT_DIR ? null : next;
|
|||
|
|
loaded = true;
|
|||
|
|
writeAtomic(configFile(), JSON.stringify({ path: configuredDir || "", updatedAt: new Date().toISOString() }, null, 2));
|
|||
|
|
return info();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function reset() {
|
|||
|
|
configuredDir = null;
|
|||
|
|
loaded = true;
|
|||
|
|
writeAtomic(configFile(), JSON.stringify({ path: "", updatedAt: new Date().toISOString() }, null, 2));
|
|||
|
|
return info();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function info() {
|
|||
|
|
const dir = get();
|
|||
|
|
return { path: dir, isDefault: dir === DEFAULT_DIR, exists: isDirectory(dir) };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
module.exports = { get, set, reset, info, isCodexDataDir };
|