Mail-Graveyard/ui-fetch-brief.md
2026-07-14 18:23:31 +02:00

99 lines
4.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Codex-Brief — UI-Fetch: Header-only + Pagination
Der Migrationspfad streamt jetzt (bewiesen: 80 MB Ordner durch eine harte
16-MiB-Grenze). **Die UI tut es nicht.** Sie ist damit das letzte OOM-Loch — und
das gefährlichere, weil ein Mensch es mit **einem Klick** auslöst.
## Das Problem
`fetchSourceFolderMessages``collectSourceMessages` ([00-router.go:1548](backend/00-router.go))
sammelt **alle Mails eines Ordners samt vollem Body** in eine Slice — nur um eine
**Liste** zu rendern. Aufgerufen an **sechs** Stellen:
`00-router.go:397, 422, 1286, 1369, 1585, 1602`.
Dasselbe auf der Zielseite: [05-imap-target.go:124](backend/05-imap-target.go) → `fetchAll`.
Rechnung für ein echtes `@dr-gold.de`-Postfach: 20.000 Mails × 200 KB =
**~4 GB RAM für einen Klick auf einen Ordner** → Container wird OOM-gekillt.
**Der Kern der Sache:** Eine Nachrichtenliste braucht **Betreff, Absender, Datum,
Flags** — sie braucht **keine Bodies**. Wir holen also Daten, die wir wegwerfen.
## Der Fix
### 1. Header-only-Fetch (neu)
`ENVELOPE` liefert Betreff/Absender/Datum/Message-ID — **ohne** `BodySection`:
```go
type MessageHeader struct {
UID uint32
MessageID string
Subject string
From string
Date time.Time
InternalDate time.Time
Flags []string
Size uint32 // RFC822.SIZE, fuer die Anzeige
}
// Fenster: neueste `limit` Mails ab `offset`. NIE unbegrenzt 1:*.
Headers(folder string, limit, offset int) ([]MessageHeader, error)
```
Implementierung (go-imap/v2) — beachte: **kein `BodySection`**:
```go
fo := &imap.FetchOptions{
Envelope: true, Flags: true, InternalDate: true, UID: true, RFC822Size: true,
// BodySection: ABSICHTLICH NICHT
}
```
Damit ist der Speicher proportional zur **Fenstergröße**, nicht zur Ordnergröße.
### 2. Fenster statt „alles"
Immer ein Bereich (z. B. die **neuesten 200**), berechnet aus
`selected.NumMessages`. Blättern über `offset`. Kein `1:*` für Listen — nirgends.
### 3. Voller Body erst beim Öffnen **einer** Mail
```go
FetchOne(folder string, uid uint32) (RawMessage, error) // mit BodySection
```
Der Lesebereich holt genau **eine** Mail. Das ist der einzige Ort, an dem ein
Body in den Speicher gehört.
### 4. Migration bleibt unangetastet
`Fetch(folder string, fn func(RawMessage) error) error` ist der **Migrationspfad**
und bleibt exakt wie er ist (inkl. der verifizierten Reihenfolge
`AlreadyCopied → dst.Append → mbox.Append → MarkCopied`). Nicht anfassen.
## Umzubauen
- Die **sechs** `fetchSourceFolderMessages`-Aufrufer auf `Headers(...)` umstellen
(Listen) bzw. auf `FetchOne(...)` (Lesebereich).
- Zielseite: `05-imap-target.go:124` (`fetchAll`) genauso.
- `collectSourceMessages` und `fetchAll` danach **löschen** — sie sind die Falle;
solange sie existieren, greift sie jemand wieder auf.
- `10-pop3.go` mitziehen (POP3 kennt keine Envelope-Fetches → dort Header aus dem
`TOP`-Kommando bauen oder die Methode als „nicht unterstuetzt" zurueckgeben).
## Kleinigkeit aus dem letzten Review (gern mitnehmen)
In `fetchEach` ([04-imap-source.go](backend/04-imap-source.go)) wurde
`defer cmd.Close()` durch explizite Closes ersetzt. Alle Pfade sind abgedeckt,
aber bei einem Panic im Callback bleibt das FETCH-Kommando offen und die
Verbindung kaputt. `defer` wieder rein (Doppel-Close ist harmlos).
## Abnahme
1. `go test ./...` grün.
2. Funktional unverändert: Listen zeigen weiterhin Betreff/Absender/Datum,
Öffnen einer Mail zeigt weiterhin den vollen Inhalt inkl. Anhängen.
3. **Speicher (fahre ich):** Ordner mit ~40 Mails à 2 MB (80 MB), Web-Container
mit harter 64-MiB-Grenze, dann per HTTP die Ordner-Liste abrufen. Vorher:
OOM-Kill. Nachher: Liste kommt, Speicher bleibt flach. Ich setze das auf und
verifiziere es — du baust nur den Code.
## NICHT in diesem Schritt
- Der lokale **mbox-Viewer** (`ReadMboxList`) liest die ganze Datei, hält aber nur
Header — Speicher ist unkritisch. Pagination dort später, reine Kosmetik.
- POP3 vollständig verdrahten, DB-Namen-Altlast (`emailforwarder.db` vs.
`mail-graveyard.db` + zwei 0-Byte-Leichen), RFC-2047-Betreff im Forward.