99 lines
4.1 KiB
Markdown
99 lines
4.1 KiB
Markdown
# 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.
|