fix: Knoepfe rutschten aus der Zeile; verwaiste Audiodateien raeumen

1. Aufnahmenliste: bei schmaler Spalte schob ein langer Titel die Knoepfe aus
der Zeile, der Papierkorb-Knopf war nicht mehr erreichbar. Flex-Kinder haben
min-width: auto, der Textblock schrumpft also nie unter seine Inhaltsbreite.
Mit der dritten Schaltflaeche (Stecknadel) reichte der Platz nicht mehr.

2. 23 sichtbare Aufnahmen = 46 Dateien, im Ordner lagen 152: 106 verwaiste mit
242 MB, Rueckstand des alten deleteRecording, das nur die DB-Zeile entfernte.
Einmalig geraeumt (Gegenprobe: keine der 106 stand in einer Zeile, danach
fehlt keine benoetigte Datei) und sweepOrphans haelt es sauber -- laeuft hinter
sweepTrash beim Start und taeglich, mit 2-h-Altersgrenze, weil eine Aufnahme
erst verschoben und dann in die DB geschrieben wird.

Die Papierkorb-Kette selbst war korrekt, mit einer Wegwerf-Zeile nachgewiesen:
loeschen -> Papierkorb (Dateien bleiben), endgueltig -> Zeile und beide Dateien
weg.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
schakal00783
2026-08-02 23:53:54 +02:00
co-authored by Claude Opus 5
parent d9554c590c
commit aa0098ae01
5 changed files with 96 additions and 2 deletions
+10
View File
@@ -460,6 +460,16 @@ export async function purgeRecording(id) {
return files;
}
// Jede Audiodatei, auf die noch eine Zeile zeigt — Papierkorb eingeschlossen,
// dessen Dateien bleiben ja absichtlich liegen. Alles andere in REC_DIR ist
// verwaist (siehe sweepOrphans in server.js).
export async function allAudioFiles() {
const [rows] = await pool.query(
'SELECT mp3_file AS f FROM recordings WHERE mp3_file IS NOT NULL' +
' UNION SELECT wav_file FROM recordings WHERE wav_file IS NOT NULL');
return rows.map(r => r.f);
}
// Ids whose retention window has run out. The caller purges them one by one so
// a single bad row cannot abort the whole sweep.
export async function expiredTrashIds() {
+36 -2
View File
@@ -544,6 +544,35 @@ async function sweepTrash() {
}
}
// Verwaiste Audiodateien: alles in REC_DIR, auf das keine Zeile in `recordings`
// mehr zeigt (auch keine im Papierkorb). Sowas hinterließ das Löschen VOR dem
// Papierkorb-Feature — es entfernte nur die Datenbankzeile, die Dateien blieben
// für immer liegen (gefunden: 108 Dateien, 247 MB). Läuft zusammen mit dem
// Papierkorb-Sweep beim Start und danach täglich.
// Die Altersgrenze ist das Sicherheitsnetz: eine Aufnahme wird erst nach REC_DIR
// verschoben und dann in die Datenbank geschrieben, dazwischen liegt ein winziges
// Fenster, in dem eine gültige Datei noch verwaist aussieht.
const ORPHAN_MIN_AGE_MS = 2 * 3600 * 1000;
async function sweepOrphans() {
let inUse;
try { inUse = await db.allAudioFiles(); }
catch (e) { return console.error('orphan sweep:', e.message); }
const keep = new Set(inUse);
let removed = 0, bytes = 0;
for (const f of fs.readdirSync(REC_DIR)) {
if (!/^rec_.*\.(mp3|wav)$/.test(f) || keep.has(f)) continue;
const p = path.join(REC_DIR, f);
try {
const st = fs.statSync(p);
if (Date.now() - st.mtimeMs < ORPHAN_MIN_AGE_MS) continue;
fs.unlinkSync(p);
removed++; bytes += st.size;
} catch (e) { if (e.code !== 'ENOENT') console.error(`orphan ${f}:`, e.message); }
}
if (removed) console.log(`orphans: ${removed} verwaiste Dateien gelöscht (${(bytes / 1048576).toFixed(0)} MB)`);
}
// --- Search ------------------------------------------------------------------
// Hits are pure SQL and come back immediately; the written answer is a separate
// call because the LLM needs ~30-90 s on this hardware (see search.js).
@@ -796,8 +825,13 @@ db.ensureSchema()
.finally(() => app.listen(PORT, '0.0.0.0', () => {
console.log(`callrec listening on :${PORT}`);
// Retention sweep at boot + daily. unref() so it never holds the process open.
sweepTrash().catch(e => console.error('trash sweep:', e.message));
setInterval(() => sweepTrash().catch(e => console.error('trash sweep:', e.message)), 24 * 3600 * 1000).unref();
const sweep = () => {
sweepTrash()
.catch(e => console.error('trash sweep:', e.message))
.then(() => sweepOrphans()); // erst leeren, dann die Reste wegräumen
};
sweep();
setInterval(sweep, 24 * 3600 * 1000).unref();
// Backfill the search index for everything recorded before this update.
// Deliberately not under the NPU lock: it is CPU/SQL only and must not
// delay a transcription that is already queued.
+8
View File
@@ -558,3 +558,11 @@ body.resizing { cursor: col-resize; user-select: none; }
.brand { cursor: pointer; user-select: none; }
.brand:hover .brand-wave { filter: brightness(1.15); }
.brand:hover h1 { color: var(--accent); }
/* Ein Flex-Kind schrumpft von Haus aus nicht unter seine Inhaltsbreite
(min-width: auto). In der Aufnahmenliste schob ein langer Titel deshalb die
Knöpfe aus der Zeile — bei schmaler Liste war der Papierkorb-Knopf nicht
mehr erreichbar und die Spalte bekam einen waagerechten Scrollbalken.
Seit der Stecknadel dazukam (drei Knöpfe) fiel das sofort auf. */
.item > .grow { min-width: 0; overflow-wrap: anywhere; }
.item > .btn { flex: none; }