mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 15:20:42 +00:00
86 lines
2.5 KiB
TypeScript
86 lines
2.5 KiB
TypeScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
|
|
const LOW_DISK_SPACE_WARNING_THRESHOLD_BYTES = 1024 * 1024 * 1024;
|
|
|
|
type DiskSpaceSnapshot = {
|
|
targetPath: string;
|
|
checkedPath: string;
|
|
availableBytes: number;
|
|
totalBytes: number | null;
|
|
};
|
|
|
|
function finiteNonNegativeNumber(value: unknown): number | null {
|
|
const numberValue = Number(value);
|
|
return Number.isFinite(numberValue) && numberValue >= 0 ? numberValue : null;
|
|
}
|
|
|
|
function findExistingDiskSpacePath(targetPath: string): string | null {
|
|
let current = path.resolve(targetPath);
|
|
while (true) {
|
|
try {
|
|
const stats = fs.statSync(current);
|
|
return stats.isDirectory() ? current : path.dirname(current);
|
|
} catch {
|
|
const parent = path.dirname(current);
|
|
if (parent === current) {
|
|
return null;
|
|
}
|
|
current = parent;
|
|
}
|
|
}
|
|
}
|
|
|
|
export function tryReadDiskSpace(targetPath: string): DiskSpaceSnapshot | null {
|
|
if (typeof fs.statfsSync !== "function") {
|
|
return null;
|
|
}
|
|
const checkedPath = findExistingDiskSpacePath(targetPath);
|
|
if (!checkedPath) {
|
|
return null;
|
|
}
|
|
try {
|
|
const stats = fs.statfsSync(checkedPath);
|
|
const blockSize = finiteNonNegativeNumber(stats.bsize);
|
|
const availableBlocks = finiteNonNegativeNumber(stats.bavail);
|
|
if (blockSize === null || availableBlocks === null) {
|
|
return null;
|
|
}
|
|
const totalBlocks = finiteNonNegativeNumber(stats.blocks);
|
|
return {
|
|
targetPath,
|
|
checkedPath,
|
|
availableBytes: blockSize * availableBlocks,
|
|
totalBytes: totalBlocks === null ? null : blockSize * totalBlocks,
|
|
};
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function formatDiskSpaceBytes(bytes: number): string {
|
|
const mib = bytes / (1024 * 1024);
|
|
if (mib < 1024) {
|
|
return `${Math.max(0, Math.round(mib))} MiB`;
|
|
}
|
|
const gib = mib / 1024;
|
|
return `${gib.toFixed(gib < 10 ? 1 : 0)} GiB`;
|
|
}
|
|
|
|
export function createLowDiskSpaceWarning(params: {
|
|
targetPath: string;
|
|
purpose: string;
|
|
thresholdBytes?: number;
|
|
}): string | null {
|
|
const thresholdBytes = params.thresholdBytes ?? LOW_DISK_SPACE_WARNING_THRESHOLD_BYTES;
|
|
const snapshot = tryReadDiskSpace(params.targetPath);
|
|
if (!snapshot || snapshot.availableBytes >= thresholdBytes) {
|
|
return null;
|
|
}
|
|
const location =
|
|
path.resolve(snapshot.targetPath) === path.resolve(snapshot.checkedPath)
|
|
? snapshot.checkedPath
|
|
: `${snapshot.targetPath} (volume checked at ${snapshot.checkedPath})`;
|
|
return `Low disk space near ${location}: ${formatDiskSpaceBytes(snapshot.availableBytes)} available; ${params.purpose} may fail.`;
|
|
}
|