fix(gui): Ventilator-Icon und Speicheranzeige auf Aufnahme-Vorrat
Das erste Icon las sich als Kleeblatt. Jetzt vier gebogene Pinwheel-Fluegel in einem feststehenden Gehaeusering; nur die Fluegel drehen. Form vorab ohne Browser geprueft (lab/preview_fan.py rendert die Bezier-Kandidaten als PNG). Die Prozentzahl misst nicht mehr die Partition, sondern den Aufnahme-Vorrat: 100 % = freier Platz + alle Audiodateien unter recordings/, belegt = diese Dateien. System, Modelle und node_modules fallen aus Zaehler und Nenner heraus; der df-Wert steht als Fusszeile in der Klappkarte. sysstat.js scannt recordings/ per readdir+stat (kein du-Prozess) und puffert 60 s; status() ist dadurch async, die Route laeuft ueber wrap(). Doku: Kapitel 07 + Changelog.
This commit is contained in:
+61
-15
@@ -78,28 +78,74 @@ function readMem() {
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
// ---- Platte -------------------------------------------------------------
|
||||
// Gemessen wird die Partition, auf der die Aufnahmen liegen — das ist der
|
||||
// Platz, der der App tatsächlich ausgeht. Die Prozentzahl folgt df: der für
|
||||
// root reservierte Anteil zählt als belegt.
|
||||
function readDisk() {
|
||||
// ---- Aufnahmen ----------------------------------------------------------
|
||||
// Summe aller Audiodateien unter recordings/. Der Scan ist billig (readdir +
|
||||
// stat, keine Lesevorgänge), wird aber gepuffert, damit ein Poll im
|
||||
// Sekundentakt nicht dauernd über das Verzeichnis läuft.
|
||||
const AUDIO_EXT = /\.(mp3|wav|m4a|opus|ogg|flac|aac)$/i;
|
||||
const REC_SCAN_TTL_MS = 60_000;
|
||||
let recCache = { bytes: 0, files: 0, at: 0 };
|
||||
let recScan = null;
|
||||
|
||||
async function scanRecordings() {
|
||||
let bytes = 0, files = 0;
|
||||
try {
|
||||
const st = fs.statfsSync(fs.existsSync(DISK_PATH) ? DISK_PATH : __dirname);
|
||||
const bs = st.bsize;
|
||||
const totalBytes = st.blocks * bs;
|
||||
const freeBytes = st.bavail * bs;
|
||||
const usedBytes = (st.blocks - st.bfree) * bs;
|
||||
const denom = usedBytes + freeBytes;
|
||||
return { totalBytes, freeBytes, usedBytes, usedPct: denom > 0 ? (usedBytes / denom) * 100 : 0 };
|
||||
} catch { return null; }
|
||||
const entries = await fs.promises.readdir(DISK_PATH, { withFileTypes: true, recursive: true });
|
||||
for (const e of entries) {
|
||||
if (!e.isFile() || !AUDIO_EXT.test(e.name)) continue;
|
||||
try {
|
||||
const st = await fs.promises.stat(path.join(e.parentPath || e.path || DISK_PATH, e.name));
|
||||
bytes += st.size; files++;
|
||||
} catch { /* zwischenzeitlich gelöscht */ }
|
||||
}
|
||||
} catch { return recCache; }
|
||||
recCache = { bytes, files, at: Date.now() };
|
||||
return recCache;
|
||||
}
|
||||
|
||||
export function status() {
|
||||
function recordingsSize() {
|
||||
const fresh = Date.now() - recCache.at < REC_SCAN_TTL_MS;
|
||||
if (!fresh && !recScan) recScan = scanRecordings().finally(() => { recScan = null; });
|
||||
// Beim allerersten Aufruf gibt es noch keine Zahl — dann auf den Scan warten,
|
||||
// sonst zeigt die Anzeige direkt nach dem Start einmal 0 % Belegung.
|
||||
return recCache.at === 0 ? recScan : recCache;
|
||||
}
|
||||
|
||||
// ---- Platte -------------------------------------------------------------
|
||||
// Zwei Sichtweisen auf denselben Datenträger:
|
||||
// partition: was df sagt (inklusive System, Modelle, node_modules)
|
||||
// budget: der Platz, um den es dem Nutzer geht — freier Platz PLUS die
|
||||
// Aufnahmen, die schon daliegen. Das ist genau das, was ein frisch
|
||||
// aufgesetztes System dieser Ausbaustufe für Aufnahmen übrig
|
||||
// hätte. Die Prozentzahl im Ring = Aufnahmen / Budget.
|
||||
async function readDisk() {
|
||||
let st;
|
||||
try { st = fs.statfsSync(fs.existsSync(DISK_PATH) ? DISK_PATH : __dirname); } catch { return null; }
|
||||
const bs = st.bsize;
|
||||
const totalBytes = st.blocks * bs;
|
||||
const freeBytes = st.bavail * bs;
|
||||
const usedBytes = (st.blocks - st.bfree) * bs;
|
||||
const partDenom = usedBytes + freeBytes;
|
||||
|
||||
const rec = await recordingsSize();
|
||||
const recBytes = rec ? rec.bytes : 0;
|
||||
const budgetBytes = freeBytes + recBytes;
|
||||
|
||||
return {
|
||||
totalBytes, freeBytes, usedBytes,
|
||||
partPct: partDenom > 0 ? (usedBytes / partDenom) * 100 : 0,
|
||||
recBytes, recFiles: rec ? rec.files : 0,
|
||||
budgetBytes,
|
||||
usedPct: budgetBytes > 0 ? (recBytes / budgetBytes) * 100 : 0
|
||||
};
|
||||
}
|
||||
|
||||
export async function status() {
|
||||
const t = readTemp();
|
||||
return {
|
||||
cpu: { pct: Math.round(cpuPct * 10) / 10, cores: os.cpus().length, load1: Math.round(os.loadavg()[0] * 100) / 100 },
|
||||
ram: readMem(),
|
||||
disk: readDisk(),
|
||||
disk: await readDisk(),
|
||||
temp: t ? { c: Math.round(t.c * 10) / 10, zone: t.zone } : null
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user