Multi-Hoster-Upload/tasks/lessons.md

10 KiB
Raw Permalink Blame History

Lessons

2026-04-21 — DOM-Doppelrender bei Bulk-State-Changes

Symptom: User klickt auf "Erneut versuchen" mit 500+ Jobs → App hängt sekundenlang. Root cause: retrySelectedJobs() ruft renderQueueTable + updateQueueActionButtons + updateStatusBar auf, startSelectedUpload() ruft direkt danach genau dieselben Funktionen nochmal auf. Regel: Wenn ein Click-Handler await anotherHandler() aufruft und der innere Handler seinen eigenen kompletten Render-Zyklus hat, NIEMALS noch einen davor. Einmal ist genug — der folgende innere Render sieht die frischen State-Mutationen ohnehin. Wie anwenden: Vor jeder await fn()-Folge in einem Handler prüfen: macht fn schon renderQueueTable()? Wenn ja, äußere Render-Calls löschen.

2026-04-21 — State-Checks MÜSSEN hinter die Semaphore-Queue

Symptom: Pre-Job-Swap prüfte _failedAccounts vor semaphore.acquire. Bei N parallelen Workers war der Check zum Start für ALLE leer — niemand hat geswapt. Erst nachdem alle im Semaphore ordentlich gewartet hatten und einer fehlschlug, wurde _failedAccounts befüllt, aber die anderen hatten ihren Check längst hinter sich. Regel: State-basierte Entscheidungen (failed accounts, overrides, cached stats) gehören direkt vor die Aktion die sie betreffen — nach jeder async await die die Position in der Queue bestimmt. Nicht am Task-Start für später wichtigen State abfragen. Wie anwenden: Bei Queue-basierten Pipelines prüfen: "Was kann sich zwischen Task-Start und dem tatsächlichen Execute ändern?" Alles was sich ändern kann, muss direkt vor dem Execute geprüft werden, nicht davor.

2026-04-21 — Reaktive Config-Updates für laufende State-Maschinen

Symptom: User fügt mid-batch einen neuen Account hinzu, aber der UploadManager merkt nicht dass die Config sich geändert hat. account-failed Event feuert nur einmal pro Account → keine zweite Re-Resolve-Chance. Regel: Wenn ein State nur bei Events neu evaluiert wird und Events "nur einmal" feuern, muss jede externe Zustandsänderung (Config-Save, User-Action) den State explizit triggern. Wie anwenden: Save-Handler müssen aktive State-Maschinen informieren. Lieber einen überflüssigen Re-Resolve-Call als einen verpassten. Für Upload-Manager: nach saveConfig → re-evaluate failed accounts ohne Override.

2026-04-21 — Error-Klassifikation: fileRejected vs accountError

Symptom: Voller Byse-Account wurde nicht rotiert — skip-rotation-file-rejected geloggt für jede Datei. Root cause: Generisches Match auf Prefix-String ("lehnte Datei ab") klassifizierte ALLE Byse-Errors als file-level, inklusive Account-voll-Meldungen. Regel: Hoster-Parser setzen den spezifischen Flag (fileRejected ODER accountError), nicht beide nie. Classifier matcht konkrete Phrasen (Duplicate, Not video format, …), niemals generische Wrapper-Strings die für mehrere Fehlerarten benutzt werden. Wie anwenden:

  • Bei neuen Hostern: per-status-Klassifikation bereits im Parser, nicht erst im Upload-Manager.
  • Classifier-Regexes auf Rejection-Kernphrasen, nicht auf UI-Prefix.
  • Defensive: accountError === true gewinnt immer gegen fileRejected — Account-Rotation ist weniger schlimm als endlose Fails auf einem toten Account.

2026-04-21 — Keine fake Build-ETAs

Symptom: User wartet 5+ min auf Tauri-Build den ich mit "1-2min" angekündigt habe. Regel: Tauri-Release-Builds brauchen real 3-6 min (Rust + NSIS + MSI). Keine Zeitangabe oder ehrlich "kann 3-6min dauern" schreiben. Wie anwenden: Wenn User nach Status fragt: sofort tail des Logs + ls des Bundle-Ordners zitieren, nicht raten.

2026-05-24 — Packaged-Electron Log-Pfade: nie __dirname/.. zum Schreiben

Symptom: doodstream-debug.log hatte auf dem Server null aktuelle Einträge; nur alte Dev-Logs. Fehler "kein Filecode" war nicht diagnostizierbar. Root cause: path.join(__dirname, '..', 'x.log') zeigt im gepackten Build in resources/app.asar (read-only). fs.appendFileSync wirft EACCES, der try/catch schluckt es → null Production-Logs. Regel: Schreibbare Pfade IMMER über app.getPath('userData') (lazy require('electron'), Fallback __dirname/.. nur für Tests/plain-node). Gilt für jede Datei die der gepackte App schreibt. Wie anwenden: Bei jedem neuen Log/Cache/State-File prüfen: wohin schreibt das im NSIS-Build? Nicht ins Install-Verzeichnis, nicht in asar.

2026-05-24 — Hoster-Fehler: echten Status surfacen, nicht generisch schlucken

Symptom: "upload_result Seite hat keinen filecode ()" — nichtssagend; User dachte doodstream-Format geändert. Root cause: XFileSharing liefert den echten Grund im st-Feld (Error: duplicate / file too big / …). Code ignorierte st komplett und warf nur den leeren Body. Regel: Bei Hoster-Parsefehlern immer die Server-Statusfelder (st/msg/code) + Kontext (welcher CDN-Node, war filecode da) in die Fehlermeldung packen. Format-Struktur unverändert + leerer Inhalt = Backend-Ablehnung, kein Parsing-Bug.

2026-05-25 — Queue leer nach Update: Auto-Dedup zu aggressiv (nicht Save/Restore)

Symptom: Queue gestoppt, App-Update -> nach Neustart Queue leer ("Dateien hierhin ziehen"). User dachte Save/Restore kaputt. Root cause: Queue WIRD korrekt gespeichert (pendingQueue) + restored. ABER _autoDeduplicateFromLog (läuft bei init nach restore) entfernte Jobs per fileName|hoster-Match gegen das GESAMTE Lifetime-fileuploader.log — UNABHÄNGIG vom Status. Pending 'preview'-Jobs, deren Datei früher mal hochgeladen wurde, flogen alle raus -> komplette Queue weg. "Update-spezifisch" nur weil der Server-App nur beim Update neustartet (normaler Restart hätte dasselbe getan). Verifiziert: Reale electron-config.json: 4 preview-Jobs, alle 4 Keys im Log -> alte Logik entfernt 4/4. Neue Logik (nur status==='done' droppen) entfernt 0/4. Regel: Auto-Cleanup/Dedup darf NIE pending/actionable User-Arbeit löschen. Nur genuin abgeschlossene ('done') Jobs decluttern. Lifetime-Logs sind Historie, nicht Session-Fortschritt — nicht als "schon erledigt"-Quelle für pending Jobs missbrauchen. Wie anwenden: Bei jeder Filter/Remove-Logik auf User-State: nach Status gaten, nicht nur nach Identitäts-Match gegen historische Daten.

2026-05-28 — Doodstream "kein Filecode": Web-Scraping ist die falsche Ebene, API ist der Fix

Symptom: Wiederkehrend "kein Filecode — Server gab leeren Link zurueck" bei großen Dateien (~1GB/7min Upload), trotz 3.3.26-3.3.29. Queue voll roter Fehler. Root cause (recherchiert + verifiziert): Der Web-Upload holt den Filecode aus einem XFileSharing-HTML-Formular. Bei langen Uploads kommt das Formular leer zurück, weil (a) der per-Seitenaufruf sess_id-Token über den 7min-Upload altert UND (b) der server-seitige File-Registration-Callback (cgi-bin/fs.cgi-Äquivalent) unter Last timeoutet → kein file_code gemintet. Wichtig: Das ist KEIN async-delay — die Datei taucht NICHT später in der Liste auf (die Registrierung, die sie listen würde, ist genau das was failt). File-list-Polling (wie Byse) hilft hier also kaum. Fix: Die offizielle doodapi.co JSON-API nutzen, wenn ein API-Key da ist — sie liefert result[0].filecode DIREKT in JSON (kein HTML-Formular) und nutzt einen persistenten api_key (kein alternder sess_id). Git-Historie: die API war der ORIGINAL-Pfad (initial commit); Web-Login kam später nur "als Alternative zum API-Key" — Key-Bevorzugung stellt also den gedachten Primärpfad wieder her, kämpft nicht gegen eine bewusste Entscheidung. Regel: Bei Hoster-Integrationen die offizielle API der Web-Scraping-Ebene vorziehen wo möglich. Empty-form/codeless-2xx = Hoster-Backend-Flake (hosterTransient), Account NICHT als tot markieren — auf BEIDEN Pfaden (Web + API) gleich klassifizieren. Voraussetzung: Engagiert nur wenn der Doodstream-Account einen gültigen API-Key hat (doodstream.com/settings). Keyless-Accounts bleiben beim Web-Pfad.

2026-05-28 — Doodstream empty-form: live diagnosis confirmed API path is the fix

Verifiziert mit echtem Account-Key (read-only API-Calls):

  • account/info → status 200, Key gültig, Storage unlimited. Premium ABGELAUFEN (2025-10-03) — Uploads gehen TROTZDEM.
  • upload/server → liefert gültigen Node (cv1130ed.cloudatacdn.com) auch ohne Premium → API-Upload-Pfad nutzbar.
  • file/list → 90.548 Dateien; Uploads landen server-seitig INTERMITTIEREND (viele Burn-Notice-Folgen genau im "Fehler"-Zeitfenster vorhanden). Das leere Formular ist also nicht "immer kaputt", sondern manchmal — der Web-Form-Registrierungs-Callback (fs-public.intconnect.net) timeoutet sporadisch. Konsequenz: API-Weg (result[0].filecode inline) umgeht den failenden Callback → richtiger Fix. file/list-Recovery ist NICHT tote Last (Dateien erscheinen ja) — aber bei 90k-Accounts MUSS man sort=created&order=desc erzwingen, sonst ist die frische Datei nicht auf Seite 1. Regel: Bei "geht manchmal/manchmal nicht" + Hoster mit offizieller API: erst per read-only API-Call (account/info, file/list) gegen den ECHTEN Account verifizieren statt am Client weiterzuraten. Das beendet Spekulations-Schleifen.

2026-06-11 — Rotation erreichte Account 4 nie (v3.3.66)

  • Hoster-Fehlertexte lügen tier-abhängig: byse "Not video file format" war für valide MKVs >2,7GB ein Account-Größen-Limit (Beweis: sauberer Size-Split 1158 OK ≤2,62GB vs. 6 rejected ≥2,81GB auf demselben Account). Eine "file rejected → Rotation sinnlos"- Klassifizierung darf nie nur auf dem Servertext beruhen, wenn die eigene File-Probe das Gegenteil beweist (probe sagt matroska → Verdikt suspekt → andere Accounts testen).
  • Optimierungen gegen dokumentierte Rescue-Pfade diffen: 1d116ac ("skip 30s poll on explicit reject") hat den Poll exakt für den Fall getötet, dessen Kommentar 5 Zeilen darüber stand. Vor jedem Guard um einen bestehenden Rettungspfad: dessen Kommentar/Tests lesen, sonst zementiert der neue Test die Regression (Test behauptete das Skip-Verhalten).
  • Jeder Retry-Pfad braucht dieselbe Fehler-Klassifizierung + dasselbe Logging wie der Hauptpfad: Der Rotations-Inner-Loop hatte weder upload-failure-Logs (Account 2 starb unsichtbar — Diagnose unmöglich) noch fileRejected-Fast-Breaks (4× 2,7GB-Re-Upload, dann Blacklist eines gesunden Accounts).
  • GitHub-Mirror-Falle: Mirror hat separate bereinigte Historie (ohne electron-config.json). gh release create pusht Tags aus der LOKALEN Historie → Credentials-Leak. Prozedur: Cherry-Pick-Worktree auf github/master, Tag auf Mirror-Commit. (Memory: feedback_github_mirror_separate_history)