mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-17 18:10:47 -04:00
converge everything into one data folder
This commit is contained in:
@@ -1,41 +1,49 @@
|
||||
// data Paths helper
|
||||
// Purpose: Resolves persistent data paths across refactors so services keep loading prior state files.
|
||||
// Scope: Preserves runtime behavior by preferring configured/canonical paths while supporting legacy locations.
|
||||
const fs = require('fs');
|
||||
// Purpose: Defines the single filesystem boundary for all mutable, persistent server data.
|
||||
// Scope: Resolves the configured data root and every application-owned mutable path beneath it.
|
||||
const path = require('path');
|
||||
|
||||
const CANONICAL_DATA_DIR = path.resolve(__dirname, '..', '..', 'data');
|
||||
const LEGACY_DATA_DIR = path.resolve(__dirname, '..', 'data');
|
||||
|
||||
function pathExists(target) {
|
||||
try {
|
||||
fs.accessSync(target, fs.constants.F_OK);
|
||||
return true;
|
||||
} catch (_err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const ROVER_SNAPSHOT_DIR_NAME = 'rover-snapshots';
|
||||
const RUNTIME_DIR_NAME = 'runtime';
|
||||
|
||||
function resolveDataDir() {
|
||||
const configured = String(process.env.SERVER_DATA_DIR || '').trim();
|
||||
if (configured) return path.resolve(configured);
|
||||
if (pathExists(CANONICAL_DATA_DIR)) return CANONICAL_DATA_DIR;
|
||||
if (pathExists(LEGACY_DATA_DIR)) return LEGACY_DATA_DIR;
|
||||
return CANONICAL_DATA_DIR;
|
||||
}
|
||||
|
||||
function resolveDataPath(fileName) {
|
||||
const configured = String(process.env.SERVER_DATA_DIR || '').trim();
|
||||
if (configured) return path.join(path.resolve(configured), fileName);
|
||||
/*
|
||||
Always join through resolveDataDir instead of repeating environment handling
|
||||
in individual services. This is what makes one SERVER_DATA_DIR mount contain
|
||||
every database, JSON store, generated file, and persistent media directory.
|
||||
*/
|
||||
return path.join(resolveDataDir(), fileName);
|
||||
}
|
||||
|
||||
const canonicalPath = path.join(CANONICAL_DATA_DIR, fileName);
|
||||
const legacyPath = path.join(LEGACY_DATA_DIR, fileName);
|
||||
if (pathExists(canonicalPath)) return canonicalPath;
|
||||
if (pathExists(legacyPath)) return legacyPath;
|
||||
return canonicalPath;
|
||||
function resolveRoverSnapshotDir() {
|
||||
/*
|
||||
Snapshot production, polling, PTZ reads, and health reporting must use the
|
||||
exact same directory. Giving this shared directory a named resolver prevents
|
||||
one of those consumers from drifting back to the former /var/lib location.
|
||||
*/
|
||||
return resolveDataPath(ROVER_SNAPSHOT_DIR_NAME);
|
||||
}
|
||||
|
||||
function resolveRuntimePath(...pathSegments) {
|
||||
/*
|
||||
Disposable files are still files intentionally managed by the Node server.
|
||||
Keeping them below a named runtime directory preserves the single-root
|
||||
filesystem contract without confusing scratch files with durable stores.
|
||||
Callers remain responsible for deleting their own completed work.
|
||||
*/
|
||||
return resolveDataPath(path.join(RUNTIME_DIR_NAME, ...pathSegments));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
resolveDataDir,
|
||||
resolveDataPath,
|
||||
resolveRoverSnapshotDir,
|
||||
resolveRuntimePath,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// Data Paths Helper Tests
|
||||
// Purpose: Pins the one-root persistence contract used by local, systemd, and future container deployments.
|
||||
// Scope: Exercises path resolution only and never creates files in the real server data directory.
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const {
|
||||
resolveDataDir,
|
||||
resolveDataPath,
|
||||
resolveRoverSnapshotDir,
|
||||
resolveRuntimePath,
|
||||
} = require('./dataPaths');
|
||||
|
||||
const originalDataDir = process.env.SERVER_DATA_DIR;
|
||||
|
||||
test.afterEach(() => {
|
||||
/*
|
||||
Environment state is process-global. Restore the caller's value after each
|
||||
assertion so this focused test remains safe when it is composed with other
|
||||
tests in the same Node process later.
|
||||
*/
|
||||
if (originalDataDir === undefined) delete process.env.SERVER_DATA_DIR;
|
||||
else process.env.SERVER_DATA_DIR = originalDataDir;
|
||||
});
|
||||
|
||||
test('defaults every persistent path to the canonical server data directory', () => {
|
||||
delete process.env.SERVER_DATA_DIR;
|
||||
const expectedRoot = path.resolve(__dirname, '..', '..', 'data');
|
||||
|
||||
assert.equal(resolveDataDir(), expectedRoot);
|
||||
assert.equal(resolveDataPath('identity.sqlite'), path.join(expectedRoot, 'identity.sqlite'));
|
||||
assert.equal(resolveRoverSnapshotDir(), path.join(expectedRoot, 'rover-snapshots'));
|
||||
assert.equal(resolveRuntimePath('replay-builds'), path.join(expectedRoot, 'runtime', 'replay-builds'));
|
||||
});
|
||||
|
||||
test('moves every persistent path beneath SERVER_DATA_DIR when it is configured', () => {
|
||||
const configuredRoot = path.join(os.tmpdir(), 'multirover-data-path-test');
|
||||
process.env.SERVER_DATA_DIR = configuredRoot;
|
||||
|
||||
assert.equal(resolveDataDir(), path.resolve(configuredRoot));
|
||||
assert.equal(resolveDataPath('fleet-reports.sqlite'), path.join(configuredRoot, 'fleet-reports.sqlite'));
|
||||
assert.equal(resolveDataPath(path.join('replays', 'example.mp4')), path.join(configuredRoot, 'replays', 'example.mp4'));
|
||||
assert.equal(resolveRoverSnapshotDir(), path.join(configuredRoot, 'rover-snapshots'));
|
||||
assert.equal(
|
||||
resolveRuntimePath('audio-forward', 'uploads'),
|
||||
path.join(configuredRoot, 'runtime', 'audio-forward', 'uploads'),
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user