src / store.ts
// simpleFiles' own data: granted folders, pending proposals, applied changes, backups, the
// change log and the file name index. All of it lives in one folder outside every project.
import * as crypto from "node:crypto";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
export function dataDir(): string {
return process.env.SIMPLEFILES_DATA ? path.resolve(process.env.SIMPLEFILES_DATA) : path.join(os.homedir(), ".lmstudio", "simplefiles");
}
export function readJson<T>(file: string, fallback: T): T {
try {
return JSON.parse(fs.readFileSync(file, "utf8")) as T;
} catch {
return fallback;
}
}
// Writes through a temporary file in the same folder, so a crash never leaves half a file.
// With a mode, the new file keeps the original's permissions (such as a script's execute bit).
export function writeFileAtomic(file: string, data: string | Buffer, mode?: number): void {
fs.mkdirSync(path.dirname(file), { recursive: true });
const tmp = path.join(path.dirname(file), `.${path.basename(file)}.simplefiles-${process.pid}-${Date.now()}.tmp`);
fs.writeFileSync(tmp, data);
if (mode !== undefined) {
try {
fs.chmodSync(tmp, mode);
} catch {
// permissions are kept where the system allows it
}
}
try {
fs.renameSync(tmp, file);
} catch (e) {
try {
fs.unlinkSync(tmp);
} catch {
// nothing to clean up
}
throw e;
}
}
export function writeJson(file: string, value: unknown): void {
writeFileAtomic(file, JSON.stringify(value, null, 2));
}
export function sha256(data: string | Buffer): string {
return crypto.createHash("sha256").update(data).digest("hex");
}
export function stamp(d = new Date()): string {
const p = (n: number) => String(n).padStart(2, "0");
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
}
export function appendLog(line: string): void {
try {
fs.mkdirSync(dataDir(), { recursive: true });
fs.appendFileSync(path.join(dataDir(), "changes.md"), `- ${stamp()} ${line}\n`, "utf8");
} catch {
// the log is a record, not a safeguard; the backups are
}
}
src / store.ts
// simpleFiles' own data: granted folders, pending proposals, applied changes, backups, the
// change log and the file name index. All of it lives in one folder outside every project.
import * as crypto from "node:crypto";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
export function dataDir(): string {
return process.env.SIMPLEFILES_DATA ? path.resolve(process.env.SIMPLEFILES_DATA) : path.join(os.homedir(), ".lmstudio", "simplefiles");
}
export function readJson<T>(file: string, fallback: T): T {
try {
return JSON.parse(fs.readFileSync(file, "utf8")) as T;
} catch {
return fallback;
}
}
// Writes through a temporary file in the same folder, so a crash never leaves half a file.
// With a mode, the new file keeps the original's permissions (such as a script's execute bit).
export function writeFileAtomic(file: string, data: string | Buffer, mode?: number): void {
fs.mkdirSync(path.dirname(file), { recursive: true });
const tmp = path.join(path.dirname(file), `.${path.basename(file)}.simplefiles-${process.pid}-${Date.now()}.tmp`);
fs.writeFileSync(tmp, data);
if (mode !== undefined) {
try {
fs.chmodSync(tmp, mode);
} catch {
// permissions are kept where the system allows it
}
}
try {
fs.renameSync(tmp, file);
} catch (e) {
try {
fs.unlinkSync(tmp);
} catch {
// nothing to clean up
}
throw e;
}
}
export function writeJson(file: string, value: unknown): void {
writeFileAtomic(file, JSON.stringify(value, null, 2));
}
export function sha256(data: string | Buffer): string {
return crypto.createHash("sha256").update(data).digest("hex");
}
export function stamp(d = new Date()): string {
const p = (n: number) => String(n).padStart(2, "0");
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
}
export function appendLog(line: string): void {
try {
fs.mkdirSync(dataDir(), { recursive: true });
fs.appendFileSync(path.join(dataDir(), "changes.md"), `- ${stamp()} ${line}\n`, "utf8");
} catch {
// the log is a record, not a safeguard; the backups are
}
}