mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-07-31 19:21:33 -07:00
Sync Stats & History window, headless --sync-cli, and Windows remote support (#160)
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import type { SyncUiSnapshotFile, SyncUiStartResult } from '../../types/sync-ui';
|
||||
|
||||
interface SyncUiSnapshotsDeps {
|
||||
snapshotsDir: string;
|
||||
nowMs: () => number;
|
||||
startRun: (kind: 'snapshot' | 'merge', host: null, args: string[]) => SyncUiStartResult;
|
||||
broadcastStateChanged: () => void;
|
||||
}
|
||||
|
||||
function formatSnapshotName(nowMs: number): string {
|
||||
// Milliseconds prevent snapshots taken in the same second from overwriting each other.
|
||||
const iso = new Date(nowMs).toISOString();
|
||||
const stamp = iso.slice(0, 23).replace(/[-:.]/g, '').replace('T', '-');
|
||||
return `immersion-${stamp}.sqlite`;
|
||||
}
|
||||
|
||||
export function createSyncUiSnapshots(deps: SyncUiSnapshotsDeps) {
|
||||
function listSnapshots(): SyncUiSnapshotFile[] {
|
||||
let names: string[];
|
||||
try {
|
||||
names = fs.readdirSync(deps.snapshotsDir);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
const files: SyncUiSnapshotFile[] = [];
|
||||
for (const name of names) {
|
||||
if (!name.endsWith('.sqlite')) continue;
|
||||
const filePath = path.join(deps.snapshotsDir, name);
|
||||
try {
|
||||
const stat = fs.statSync(filePath);
|
||||
if (!stat.isFile()) continue;
|
||||
files.push({
|
||||
path: filePath,
|
||||
name,
|
||||
sizeBytes: stat.size,
|
||||
modifiedAtMs: stat.mtimeMs,
|
||||
});
|
||||
} catch {
|
||||
// file disappeared between readdir and stat
|
||||
}
|
||||
}
|
||||
files.sort((a, b) => b.modifiedAtMs - a.modifiedAtMs);
|
||||
return files;
|
||||
}
|
||||
|
||||
function createSnapshot(): SyncUiStartResult {
|
||||
const outPath = path.join(deps.snapshotsDir, formatSnapshotName(deps.nowMs()));
|
||||
return deps.startRun('snapshot', null, ['sync', '--snapshot', outPath, '--json']);
|
||||
}
|
||||
|
||||
function mergeSnapshotFile(filePath: string, force = false): SyncUiStartResult {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return { started: false, runId: null, reason: `Snapshot file not found: ${filePath}` };
|
||||
}
|
||||
const args = ['sync', '--merge', filePath];
|
||||
if (force) args.push('--force');
|
||||
args.push('--json');
|
||||
return deps.startRun('merge', null, args);
|
||||
}
|
||||
|
||||
function deleteSnapshot(filePath: string): void {
|
||||
const resolved = path.resolve(filePath);
|
||||
const dir = path.resolve(deps.snapshotsDir);
|
||||
if (resolved !== path.join(dir, path.basename(resolved)) || !resolved.endsWith('.sqlite')) {
|
||||
throw new Error('Refusing to delete a file outside the snapshots directory.');
|
||||
}
|
||||
fs.rmSync(resolved, { force: true });
|
||||
deps.broadcastStateChanged();
|
||||
}
|
||||
|
||||
return { listSnapshots, createSnapshot, mergeSnapshotFile, deleteSnapshot };
|
||||
}
|
||||
Reference in New Issue
Block a user