From 551f1d7b2c7de030844f5d7c880c1cdbcf16f042 Mon Sep 17 00:00:00 2001 From: Hitonabi Date: Sat, 12 Sep 2026 21:33:22 +0200 Subject: [PATCH] =?UTF-8?q?perf(v5):=20Kompressions-Fortschritt=20als=20kl?= =?UTF-8?q?eine=20Nachricht,=20Roh-Gr=C3=B6=C3=9Fen=2030=20s=20gemerkt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WAS: Ein Prozent-Tick von HandBrake (etwa einmal je Sekunde) geht jetzt als `kompression-stand` (nur die Warteschlange) an Haupt und Fenster; der volle Vorgangs-Stand — mit rekursivem Ordnerlauf durch jeden Roh-Ordner, fremden Ordnern und zwei statfs — kommt nur noch bei einem Phasenwechsel oder auf Zuruf. Roh-Größen werden 30 s gemerkt; Löschen, Aufräumen und der Knopf „Aktualisieren" messen frisch. WARUM: Durchsicht 12.09.2026, Fund F7 — auf einer NAS als Arbeitsordner war das ein Netz-Ordnerlauf pro Sekunde, und das Fenster zeichnete jede Sekunde komplett neu. Co-Authored-By: Claude Opus 5 --- rippy-windows/src/fenster/kernverbindung.ts | 4 ++ rippy-windows/src/gemeinsam/nachrichten.ts | 5 ++ rippy-windows/src/haupt/index.ts | 3 ++ .../src/kern/ablage/rohaufraeumen.ts | 4 +- rippy-windows/src/kern/dienste/vorgaenge.ts | 51 +++++++++++++++++-- rippy-windows/src/kern/index.ts | 2 +- 6 files changed, 62 insertions(+), 7 deletions(-) diff --git a/rippy-windows/src/fenster/kernverbindung.ts b/rippy-windows/src/fenster/kernverbindung.ts index 5c40b1a..4a71a28 100644 --- a/rippy-windows/src/fenster/kernverbindung.ts +++ b/rippy-windows/src/fenster/kernverbindung.ts @@ -213,6 +213,10 @@ function kernNachricht(nachricht: KernNachricht): void { case 'vorgaenge': stand.vorgaenge = nachricht.stand break + case 'kompression-stand': + // 5.7.0 (F7): nur die Warteschlange frisch — der Rest bleibt stehen. + if (stand.vorgaenge !== null) stand.vorgaenge = { ...stand.vorgaenge, kompression: nachricht.kompression } + break case 'kompression-ergebnis': meldung(`${nachricht.ok ? '' : '⚠ '}Kompression „${nachricht.titel}": ${nachricht.text}`) break diff --git a/rippy-windows/src/gemeinsam/nachrichten.ts b/rippy-windows/src/gemeinsam/nachrichten.ts index b8b0f01..6ebb971 100644 --- a/rippy-windows/src/gemeinsam/nachrichten.ts +++ b/rippy-windows/src/gemeinsam/nachrichten.ts @@ -414,6 +414,9 @@ export type KernNachricht = /** Alle Vorgänge, die Roh-Belegung und die Kompressions-Warteschlange * (5.4.0) — geht auch ans Haupt (Taskleiste, Updates warten). */ | { art: 'vorgaenge'; stand: VorgaengeStand } + /** 5.7.0 (F7): NUR die Warteschlange — jeder Prozent-Tick der Kompression + * kommt so, ohne den teuren vollen Vorgangs-Stand (Ordnerläufe, statfs). */ + | { art: 'kompression-stand'; kompression: VorgaengeStand['kompression'] } /** Eine Kompression ist zu Ende — fürs Toast und die Ereignis-Zeile. */ | { art: 'kompression-ergebnis'; vorgangId: string; titel: string; ok: boolean; text: string } | { art: 'laufwerk-gesundheit'; id: string; bericht: LaufwerkBericht } @@ -594,6 +597,8 @@ export function istKernNachricht(wert: unknown): wert is KernNachricht { istObjekt((wert.stand as Record).kompression) && istObjekt((wert.stand as Record).statistik) ) + case 'kompression-stand': + return istObjekt(wert.kompression) && Array.isArray((wert.kompression as Record).wartend) case 'kompression-ergebnis': return ( istKennung(wert.vorgangId) && diff --git a/rippy-windows/src/haupt/index.ts b/rippy-windows/src/haupt/index.ts index c65f74d..bd3bb0d 100644 --- a/rippy-windows/src/haupt/index.ts +++ b/rippy-windows/src/haupt/index.ts @@ -121,6 +121,9 @@ function start(): void { // 5.4.0: Die Kompression läuft OHNE Laufwerk weiter — Updates // warten auch auf sie, und die Taskleiste zeigt ihren Fortschritt. kompressionVerarbeiten(nachricht.stand.kompression.laeuft) + } else if (nachricht.art === 'kompression-stand') { + // 5.7.0 (F7): der leichte Fortschritts-Tick — dieselbe Wirkung. + kompressionVerarbeiten(nachricht.kompression.laeuft) } else if (nachricht.art === 'kompression-ergebnis' && !ohneNebenwirkungen) { toast(nachricht.ok ? 'Rippy ist fertig' : 'Rippy: Kompression fehlgeschlagen', `${nachricht.titel}: ${nachricht.text}`) } else if (nachricht.art === 'disc-info' && nachricht.info.titel.length > 0 && !ohneNebenwirkungen) { diff --git a/rippy-windows/src/kern/ablage/rohaufraeumen.ts b/rippy-windows/src/kern/ablage/rohaufraeumen.ts index bd51835..203da3f 100644 --- a/rippy-windows/src/kern/ablage/rohaufraeumen.ts +++ b/rippy-windows/src/kern/ablage/rohaufraeumen.ts @@ -165,7 +165,7 @@ export function ordnerGroesse(pfad: string): number { } /** Ordner unter roh\, die kein Vorgang kennt — nur zeigen, nie von selbst löschen. */ -export function fremdeOrdner(rohWurzel: string, bekannte: ReadonlySet): FremderOrdner[] { +export function fremdeOrdner(rohWurzel: string, bekannte: ReadonlySet, groesse: (pfad: string) => number = ordnerGroesse): FremderOrdner[] { if (!existsSync(rohWurzel)) return [] const liste: FremderOrdner[] = [] let namen: string[] @@ -182,7 +182,7 @@ export function fremdeOrdner(rohWurzel: string, bekannte: ReadonlySet): for (const name of namen) { const voll = join(rohWurzel, name) if (bekannte.has(voll) || bekannte.has(name)) continue - liste.push({ ordner: voll, bytes: ordnerGroesse(voll) }) + liste.push({ ordner: voll, bytes: groesse(voll) }) } return liste.sort((a, b) => b.bytes - a.bytes) } diff --git a/rippy-windows/src/kern/dienste/vorgaenge.ts b/rippy-windows/src/kern/dienste/vorgaenge.ts index 7528455..c4cf371 100644 --- a/rippy-windows/src/kern/dienste/vorgaenge.ts +++ b/rippy-windows/src/kern/dienste/vorgaenge.ts @@ -115,12 +115,46 @@ export class VorgangDienst { } } + /** Jede Änderung der Warteschlange — Fortschritt eingeschlossen. 5.7.0 + * (Durchsicht F7): Ein Prozent-Tick ist eine KLEINE Nachricht + * (kompression-stand); vorher löste jeder Tick (HandBrake: etwa einmal je + * Sekunde) den vollen Vorgangs-Stand aus — mit einem Ordnerlauf durch + * jeden Roh-Ordner, zwei statfs und einem kompletten Neuzeichnen des + * Fensters. Der volle Stand geht nur noch bei einem Phasenwechsel. */ private standGeaendert(laeuft: KompressionStand | null): void { - if (laeuft !== null && this.letztePhase.get(laeuft.vorgangId) !== laeuft.phase) { + const phasenwechsel = laeuft !== null && this.letztePhase.get(laeuft.vorgangId) !== laeuft.phase + if (laeuft !== null && phasenwechsel) { this.letztePhase.set(laeuft.vorgangId, laeuft.phase) this.db?.vorgangAktualisieren(laeuft.vorgangId, { phase: laeuft.phase === 'legt-ab' ? 'legt-ab' : 'komprimiert' }) } - this.senden() + if (phasenwechsel || laeuft === null) { + this.senden() + return + } + this.kontext.anHauptUndFenster({ + art: 'kompression-stand', + kompression: { ...this.warteschlange.stand(), pausiert: this.warteschlange.istPausiert() }, + }) + } + + // Roh-Größen sind teuer (rekursiver Ordnerlauf, bei einer NAS über das + // Netz) und ändern sich nur, wenn Rippy selbst etwas anlegt oder löscht — + // deshalb 30 s lang gemerkt (F7). Wer löscht, leert das Gedächtnis. + private groessen = new Map() + private static readonly GROESSEN_FRISCH_MS = 30_000 + + private groesseGemerkt(pfad: string): number { + const jetzt = Date.now() + const alt = this.groessen.get(pfad) + if (alt !== undefined && jetzt - alt.wann < VorgangDienst.GROESSEN_FRISCH_MS) return alt.bytes + const bytes = ordnerGroesse(pfad) + this.groessen.set(pfad, { bytes, wann: jetzt }) + return bytes + } + + /** Nach Löschen oder Aufräumen: frisch messen. */ + private groessenVergessen(): void { + this.groessen.clear() } private async ergebnis(auftrag: KompressionsAuftrag, ergebnis: AblageErgebnis): Promise { @@ -220,7 +254,7 @@ export class VorgangDienst { let rohGesamtBytes = 0 const vorgaenge: VorgangEintrag[] = liste.map((v) => { const rohDa = v.rohGeloeschtAm === null && existsSync(v.rohOrdner) - const rohBytes = rohDa ? ordnerGroesse(v.rohOrdner) : 0 + const rohBytes = rohDa ? this.groesseGemerkt(v.rohOrdner) : 0 rohGesamtBytes += rohBytes const auftrag = auftragAusJson(v.auftrag) const ruhig = v.phase === 'fertig' || v.phase === 'fehler' || v.phase === 'abgebrochen' @@ -247,7 +281,7 @@ export class VorgangDienst { } }) const bekannte = new Set(liste.map((v) => v.rohOrdner)) - const fremde = fremdeOrdner(this.einstellungen.rohWurzel(), bekannte) + const fremde = fremdeOrdner(this.einstellungen.rohWurzel(), bekannte, (pfad) => this.groesseGemerkt(pfad)) for (const f of fremde) rohGesamtBytes += f.bytes return { vorgaenge, @@ -259,6 +293,12 @@ export class VorgangDienst { } } + /** Der Knopf „Aktualisieren": frisch messen, nicht aus dem Gedächtnis. */ + neuMessen(): void { + this.groessenVergessen() + this.senden() + } + senden(): void { try { this.kontext.anHauptUndFenster({ art: 'vorgaenge', stand: this.stand() }) @@ -290,6 +330,7 @@ export class VorgangDienst { fertigeDateien: dateienAusJson(v.fertigeDateien, (g) => this.kontext.protokoll.schreiben(`${v.id}: ${g}`)), })) const ergebnis = aufraeumen(kandidaten, regel, new Date()) + if (ergebnis.geloescht.length > 0) this.groessenVergessen() const jetzt = new Date().toISOString() for (const id of ergebnis.geloescht) { this.db.vorgangAktualisieren(id, { rohGeloeschtAm: jetzt, rohBytes: this.db.vorgangLesen(id)?.rohBytes ?? 0 }) @@ -321,6 +362,7 @@ export class VorgangDienst { } try { rmSync(v.rohOrdner, { recursive: true, force: true }) + this.groessenVergessen() this.db?.vorgangAktualisieren(vorgangId, { rohGeloeschtAm: new Date().toISOString() }) this.kontext.hinweis(`Roh-Dateien von „${v.titel}" gelöscht (von Hand).`) } catch (fehler) { @@ -341,6 +383,7 @@ export class VorgangDienst { } try { rmSync(ordner, { recursive: true, force: true }) + this.groessenVergessen() this.kontext.hinweis(`Ordner „${ordner}" gelöscht (von Hand).`) } catch (fehler) { this.kontext.fehler(`„${ordner}" nicht gelöscht: ${String(fehler)}`) diff --git a/rippy-windows/src/kern/index.ts b/rippy-windows/src/kern/index.ts index b7a9d8c..92f98ab 100644 --- a/rippy-windows/src/kern/index.ts +++ b/rippy-windows/src/kern/index.ts @@ -289,7 +289,7 @@ function fensterNachricht(frage: unknown): void { void werkzeuge.schluesselLauf().then(() => werkzeuge.senden()) return case 'vorgaenge-laden': - vorgaenge.senden() + vorgaenge.neuMessen() return case 'vorgang-roh-loeschen': vorgaenge.rohLoeschen(frage.vorgangId)