From aa0098ae01493e4ae3161036dd4c547d939e8577 Mon Sep 17 00:00:00 2001 From: schakal00783 Date: Sun, 2 Aug 2026 23:53:54 +0200 Subject: [PATCH] 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) --- app/db.js | 10 +++++++++ app/server.js | 38 +++++++++++++++++++++++++++++++-- app/web/src/style.css | 8 +++++++ docs/08-changelog.md | 25 ++++++++++++++++++++++ docs/09-suche-und-verwaltung.md | 17 +++++++++++++++ 5 files changed, 96 insertions(+), 2 deletions(-) diff --git a/app/db.js b/app/db.js index c8709b0..7b1d888 100644 --- a/app/db.js +++ b/app/db.js @@ -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() { diff --git a/app/server.js b/app/server.js index f21ffdc..33fe333 100644 --- a/app/server.js +++ b/app/server.js @@ -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. diff --git a/app/web/src/style.css b/app/web/src/style.css index 2b5021b..063f656 100644 --- a/app/web/src/style.css +++ b/app/web/src/style.css @@ -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; } diff --git a/docs/08-changelog.md b/docs/08-changelog.md index c7328b7..13290f3 100644 --- a/docs/08-changelog.md +++ b/docs/08-changelog.md @@ -3,6 +3,31 @@ Chronik der Entwicklungsschritte. **Regel: jede zukünftige Änderung bekommt hier einen Eintrag** (Datum, Was, Warum, betroffene Dateien/Backups). +## 2026-08-02 (spät) — Knöpfe rutschten aus der Zeile, 242 MB Dateileichen geräumt + +**1. Zeilenumbruch in der Aufnahmenliste.** Bei schmaler Liste schob ein langer Titel die Knöpfe aus der +Zeile — der Papierkorb-Knopf der obersten Aufnahme war nicht mehr erreichbar, die Spalte bekam einen +waagerechten Scrollbalken. Ursache ist die Vorgabe `min-width: auto` für Flex-Kinder: der Textblock +schrumpft von sich aus nie unter seine Inhaltsbreite. Mit der dritten Schaltfläche (Stecknadel) reichte +der Platz dann nicht mehr. + +```css +.item > .grow { min-width: 0; overflow-wrap: anywhere; } +.item > .btn { flex: none; } +``` + +**2. Gelöschte Aufnahmen lagen weiter auf der Karte.** 23 sichtbare Aufnahmen (46 Dateien), aber 152 +Dateien im Ordner: **106 verwaiste mit 242 MB**, Rückstand des alten `deleteRecording`, das nur die +Datenbankzeile entfernte. Einmalig geräumt (Gegenprobe vorher: keine der 106 Dateien stand in einer +Zeile, danach: keine benötigte Datei fehlt), und `sweepOrphans` in `server.js` verhindert den Rückfall — +Details in [09 – Suche & Verwaltung](09-suche-und-verwaltung.md). + +Die Papierkorb-Kette selbst war in Ordnung und wurde mit einer Wegwerf-Zeile nachgewiesen: löschen → +Papierkorb, Dateien bleiben; endgültig löschen → `{"files":2}`, Zeile und beide Dateien weg. + +Dateien: `app/server.js`, `app/db.js` (`allAudioFiles`), `app/web/src/style.css`. +Board-Backups: `.bak-orphans`. + ## 2026-08-02 — Wortmarkierung lief dem Ton voraus: VBR-MP3 war schuld **Symptom:** im Transkript war ein Wort markiert, gesprochen wurde aber eins rund vier Zeilen weiter oben. diff --git a/docs/09-suche-und-verwaltung.md b/docs/09-suche-und-verwaltung.md index e1099c0..49a7f0d 100644 --- a/docs/09-suche-und-verwaltung.md +++ b/docs/09-suche-und-verwaltung.md @@ -40,6 +40,23 @@ verpassen. Die `words`-Zeilen blieben verwaist in der Datenbank zurück und die Audiodateien für immer auf der Karte. `purgeRecording` räumt jetzt beides mit ab. +### Verwaiste Dateien (Aufräumlauf, 2026-08-02) + +Der Altfehler hatte über Monate Spuren hinterlassen: **106 Dateien mit 242 MB**, auf die keine Zeile mehr +zeigte — bei 23 echten Aufnahmen (46 Dateien) lag also mehr Müll als Nutzdaten auf der Karte. + +`sweepOrphans` in `server.js` räumt das jetzt automatisch: alles in `recordings/`, was dem Muster +`rec_*.mp3|wav` entspricht und in **keiner** Zeile vorkommt (Papierkorb eingeschlossen — dessen Dateien +bleiben ja absichtlich liegen), wird gelöscht. Läuft direkt hinter `sweepTrash`, beim Start und danach +täglich. + +**Altersgrenze 2 h als Sicherheitsnetz:** beim Beenden einer Aufnahme wird die Datei erst nach +`recordings/` verschoben und danach die Zeile geschrieben. In diesem Fenster sähe eine völlig gültige +Datei verwaist aus. Nachgemessen: eine drei Stunden alte Attrappe wird geräumt, eine frische bleibt liegen. + +Damit gilt durchgehend: löschen ⇒ Papierkorb (Dateien bleiben), dort endgültig löschen ⇒ Zeile **und** +WAV/MP3 sind weg. End-to-end geprüft. + Retention über `AIME_TRASH_DAYS` einstellbar (Vorgabe 30). ## Inhaltssuche