Uebertragung eines Befunds aus dem Security-Audit der alten Melo-App
(21.08.2026) in die neue Codebasis.
Ausgangslage hier war eine ANDERE als dort: LoggerService lud jeden Fehler
unbedingt zu logs.baka-net.de hoch, es gab ueberhaupt keinen Schalter. In
der alten App existierte er zwar, wirkte aber nicht ueber einen Neustart
hinaus.
Neu:
- Schalter "Fehlerberichte senden" in den Einstellungen unter Datenschutz,
Standard AUS. Ohne ausdrueckliche Zustimmung verlaesst kein Bericht das
Geraet - auch nicht beim allerersten Start und auch nicht im Zeitfenster
zwischen logger.init() und settings.init().
- Sperre an der einzigen Stelle, durch die jeder Upload laeuft. Lokales
debugPrint bleibt, aber nichts verlaesst das Geraet; der Puffer wird
geleert, damit er nicht unbegrenzt waechst.
- Die Entscheidung ueberlebt den Neustart: AppSettings speichert sie und
reicht sie an den Logger durch, UND LoggerService liest sie beim Start
zusaetzlich selbst.
- LoggerService bekam einen injizierbaren http.Client (Muster wie BakaAuth),
damit sich belegen laesst, dass ohne Zustimmung KEIN Request rausgeht.
Auswirkung auf bestehende Installationen: Wer bisher unfreiwillig Berichte
gesendet hat, sendet ab diesem Update keine mehr, bis er zustimmt. Das ist
gewollt - die Zustimmung wurde nie eingeholt.
Nicht uebernommen: der zweite Befund (Logout liess Zugangsdaten zurueck)
trifft hier nicht zu. BakaAuth.abmelden() loescht genau die Schluessel, die
anmelden() schreibt, und Navidrome hat mit loescheZugangsdaten() ein
eigenes, vollstaendiges Trennen.
Auf main (34d6d8e) rebast. settings_screen.dart hat sich sauber automatisch
zusammengefuehrt - die Umbenennungen und MeloTheme-Farben der UI-Politur
bleiben erhalten, der Datenschutz-Abschnitt fuegt sich ein. Im CHANGELOG
kollidierten beide Seiten am Kopf; beide Eintraege wurden behalten.
Tests: 340 gruen (main allein: 328), 12 neue in
test/settings/diagnose_opt_out_test.dart. Fuenf tragende Stellen per
Mutationsprobe geprueft. flutter analyze: keine Befunde.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QbuWT267W8rFDsaavANuEn
Auf Wunsch: was vom Handy hochgeladen wird, soll auch in Navidrome
auftauchen. Die Subsonic-API kennt keinen Upload — also erledigt es die
Melo-Cloud selbst.
Server (/home/dustin/scripts/melo_cloud.py, nicht versioniert — Backup als
melo_cloud.py.bak-20260821-084506):
- verknuepfe_navidrome(): Hardlink der Registry-Datei nach
/home/dustin/navidrome/music (Bind-Mount des Containers). Gleiche
Partition -> kein zusaetzlicher Speicher. Fallback: Kopie.
- rescan_navidrome(): Subsonic startScan.view ueber Navidromes
AuthProxyHeader (TrustedSources 127.0.0.1) — kein Passwort im Code.
Nur zur Beschleunigung, der Watcher findet neue Dateien ohnehin.
- upload() verknuepft neue UND bereits bekannte Titel (heilt Bestand).
- handle_delete() entfernt die Datei aus Registry + Navidrome, sobald kein
Konto sie mehr aktiv hat. Der Registry-EINTRAG bleibt stehen: handle_list
haengt die Grabsteine daran (JOIN registry) — ohne ihn erfuehren die
anderen Geraete nie von der Loeschung (Zombie-Song).
- registry_pfad(): gemeinsame Dateisuche ueber alle Audio-Endungen, mit
Rueckfall auf den Navidrome-Ordner.
Nebenbei repariert:
- Alle 325 Cloud-Dateien lagen nur noch im Navidrome-Ordner, REG war leer:
jeder Download antwortete "File missing". Per Hardlink zurueckverknuepft
(scripts/melo_cloud_migriere_registry.py).
- upload() legte jede Datei als ".mp3" ab, auch m4a/flac. Jetzt echte
Endung; _download liefert passenden Content-Type und einen Dateinamen
ohne doppelte Endung.
App:
- MeloCloudService.herunterladen() gibt die geschriebene Datei zurueck und
leitet die Endung aus dem Content-Type ab (endungFuer) — sonst landet
eine M4A als .mp3 auf dem Handy und Android ordnet sie falsch ein.
- loeschBremseGreift(): der Abgleich reicht keine Loeschwelle mehr zum
Server durch (>10 Titel UND >1/3 des Serverbestands). Ohne die Bremse
haette eine nicht eingehaengte Speicherkarte die Sammlung auf allen
Geraeten geloescht.
276 Tests gruen (1 uebersprungen), flutter analyze ohne Befund, plus ein
Durchlauf gegen den echten Server (scripts/test_melo_cloud_navidrome.py):
Upload -> Registry + Navidrome (ein Hardlink) -> Navidrome liest ein ->
Loeschen entfernt beides, Grabstein bleibt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FpPu4nuKjKKeX1RpdDeX81
BUG 1 — Lieder vom Handy waren nicht abspielbar
("Wiedergabe fehlgeschlagen: (0) Source error" / "Loading interrupted")
Wurzel-Ursache: MeloAudioHandler.loadPlaylist hat jeden Warteschlangen-
Eintrag durch NavidromeService.streamAndCacheToLocal(item.id) geschickt,
sobald Zugangsdaten existierten. item.id ist aber NIE eine Navidrome-Song-ID
— lokal ist es file:///storage/..., beim Server die fertige Stream-Adresse.
Der Server bekam also 'file:///...' als Song-ID, antwortete mit einem Fehler,
und diese Fehlerantwort wurde an just_audio weitergereicht (Source error) —
bzw. als .mp3 in den Cache geschrieben, wodurch der Titel dauerhaft kaputt
blieb.
Zweite Ursache: die Schleife lud die GANZE Warteschlange seriell vorab
herunter (30s Timeout je Titel), bevor setAudioSources lief. Bei hunderten
Titeln startete die Wiedergabe deshalb nie; ein zweiter Tipp brach den
laufenden Ladevorgang ab ("Loading interrupted").
Fix:
- Server-Titel tragen ihre ID in MediaItem.extras['navidromeId'] statt sie
aus der Abspiel-Adresse zu raten. Neue reine Funktionen navidromeIdOf,
songIdOf, nutztServerCache, quelleFuer.
- loadPlaylist baut die Quellen ohne Netzzugriff; Caching des laufenden
Titels im Hintergrund (unawaited).
- Resume/Scrobble nur noch mit der jeweils passenden ID (Server bzw. lokal).
- ladeInCache() ersetzt streamAndCacheToLocal(): .part-Datei, Pruefung des
Inhaltstyps (istAudioAntwort), stabiler Cache-Schluessel ueber die
Song-ID statt der Stream-Adresse (die trug Token+Salt und war je Sitzung
anders — der Cache war nie wiederauffindbar), Client wird geschlossen.
BUG 2 — kein Abgleich zwischen Handy und Server
Neu: services/melo_cloud_service.dart + services/sync_service.dart gegen
cloud.baka-net.de (Bearer-JWT ueber BakaAuth). Server-Titel herunterladen
(offline verfuegbar), eigene Dateien hochladen, Loeschungen in beide
Richtungen (Tombstones), Favoriten und Wiedergabe-Verlauf. Automatisch beim
App-Start und bei Rueckkehr in die App (max. alle 15 Min), plus Knopf unter
Einstellungen -> Geraete-Abgleich. Reine Planungsfunktion planeSync().
Bewusst NICHT ueber Navidrome: die Subsonic-API kennt keinen Upload-
Endpunkt. Navidrome bleibt die Streaming-Bibliothek, die Melo-Cloud ist der
gemeinsame Speicher.
DB-Schema 8: songs.cloud_id verbindet Geraet und Server.
Nebenbei behoben (blockierte Build bzw. Tests):
- database.g.dart war veraltet — das Projekt liess sich nicht uebersetzen.
- metadataEdited wurde nirgends gesetzt/beachtet: von Hand korrigierte
Metadaten wurden vom naechsten Scan ueberschrieben. Jetzt in beiden
Scans respektiert; metadatenUebernahme() setzt die Markierung.
- song_detail_sheet_test.dart haengt beim Oeffnen des Modal-Sheets und
blockierte den gesamten Testlauf — vorerst uebersprungen (TODO im Code);
der Zweck wird von metadaten_uebernahme_test.dart abgedeckt.
Enthaelt ausserdem die bis dahin nicht committete Arbeit der Vorsitzung
(Musikerkennung/ACRCloud, MusicBrainz-Metadaten, MediaStore-Datentraeger).
267 Tests gruen (1 uebersprungen), flutter analyze ohne Befund.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FpPu4nuKjKKeX1RpdDeX81
- AutoScan: beim Zurueckkehren in die App wird die Zahl der Musikdateien
mit der Bibliothek verglichen; nur bei Abweichung laeuft ein Scan, und
hoechstens alle 5 Minuten. Gleich viele Loeschungen wie Neuzugaenge
bleiben unbemerkt - dafuer gibt es den Scan von Hand.
- ServerNeuheiten: meldet neu auf Navidrome aufgetauchte Alben im
Online-Tab. Der erste Abruf meldet bewusst nichts, sonst waere die ganze
Sammlung "neu"; er nutzt die ohnehin geladenen Alben statt eines zweiten
Serveraufrufs.
- Neu: MeloDb.countSongs(), countAndroidMediaStoreSongs().
189 Tests gruen, flutter analyze ohne Befund.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018NLW1q1hzEC9goDQ1dJ98d
Neuer YouTube-Bereich im Online-Tab: Adresse einfuegen, herunterladen, der
Titel landet in Meine Musik.
- BakaAuth: Anmeldung gegen baka-net.de/auth, Token verschluesselt auf dem
Geraet. Der Server meldet Fehler mit HTTP 200 und Text im Rumpf, deshalb
wird der Inhalt geprueft statt nur der Statuscode.
- YtDownloadService: Proxy fragen, MP3 abholen, halbe Dateien aufraeumen.
Kuenstler nur aus "Kuenstler - Lied", sonst leer statt geraten.
- MediaStoreBridge (Kotlin): legt die Datei in Music/Melo ab und meldet sie
dem MediaStore. Noetig, weil der Android-Scan sonst beim naechsten Lauf
alles wieder als verschwunden markiert.
- SubTabs nach shared/ gezogen (jetzt in Meine Musik und Online genutzt).
177 Tests gruen, flutter analyze ohne Befund, Release-APK baut.
Der Weg ueber den echten Proxy ist noch nicht auf dem Geraet erprobt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018NLW1q1hzEC9goDQ1dJ98d
Tiefe Debug-Runde bewiesen: Server, DB-Passwort und App-Token-Logik sind
korrekt (8/8-Repro gegen Live-Server) — abgelehnt wird ein falscher
Passwort-Wert vom Gerät. Damit das künftig sofort sichtbar ist:
- NavidromeService.letzterLoginFehler hält die Server-Meldung fest
- Login-Dialog zeigt sie statt generischem "Login fehlgeschlagen"
- Fehler-Log enthält die Passwort-Länge (nie das Passwort selbst)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JRj3pupcigUi683vy2Wwnz
Diagnose-Logs werden zentral bereinigt, bevor sie an logs.baka-net.de gehen.
Lokaler debugPrint bleibt vollständig.
- Freitext-Scrubber (LoggerService.scrubText) für message + stackTrace:
maskiert user=/pw=/password=/token=/secret=/url= sowie Datei-Pfade,
nackte URLs, Bearer-Token, JWTs und lange Hex-IDs.
- stackTrace auf 8 Zeilen gekürzt (capFrames).
- deviceModel nur noch grobe Plattform (android/ios) statt Hersteller+Modell;
device_info_plus im Logger entfernt.
- Quelle entschärft: settings_screen loggte Navidrome-Login mit Klartext
user=/url= — jetzt nur neutraler Status inkl. pwLeer-Flag.
- Neue Tests: test/logger_service_test.dart (9). 79/79 grün, analyze sauber.
Opt-in/Einwilligung bleibt separater Folgeschritt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tr2UwurtxyJUpWpBSeVmBm
ROOT CAUSE für die fehlenden Logs — und für den fehlschlagenden
Navidrome-Login: Flutter injiziert android.permission.INTERNET nur in
debug/ und profile/AndroidManifest.xml. In main/AndroidManifest.xml
stand sie nicht, das Release-APK hatte damit gar keinen Netzzugriff.
Jede http-Anfrage schlug sofort mit SocketException fehl.
- INTERNET + ACCESS_NETWORK_STATE in main/AndroidManifest.xml ergänzt
- Login-Versuch/-Erfolg/-Fehlschlag im Settings-Dialog explizit geloggt
(URL + User + ob das Passwortfeld leer war; das Passwort selbst nicht)
- Batch-Schwelle 20 -> 5, damit INFO-Logs auch ohne vorherigen Fehler
hochgeladen werden
Nicht geaendert: der Sofort-Upload bei ERROR war bereits implementiert
(error() ruft _uploadIfError -> _uploadLogs direkt auf) und war nicht
die Ursache.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJzQjUtnvYUtHdnTs3iCru
Bisher stand im Log nur die nackte Exception-Message ("Connection timeout")
— ohne URL und ohne Fehlertyp war die Ursache nicht eingrenzbar.
Neu im Fehler-Log:
- Fehlerart klassifiziert: TIMEOUT / SOCKET / TLS-HANDSHAKE / HTTP / FORMAT
- SocketException zusätzlich mit osError-Message, errorCode, Adresse und
Adress-Typ (IPv4 vs IPv6 — zeigt ein IPv6-Problem direkt an)
- Server-URL sowie geparstes scheme/host/port (falsche URL, fehlendes
https, DNS-Auflösung erkennbar)
- e.runtimeType + Message
Bewusst NICHT geloggt: die volle Ping-URI — sie enthält Auth-Token und
Salt als Query-Parameter, die nicht auf dem Log-Server landen sollen.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJzQjUtnvYUtHdnTs3iCru
- getPlaylists() + getPlaylistSongs() APIs für Subsonic-Playlist-Laden
- syncPlaylistsFromServer() in PlaylistService importiert alle Server-Playlisten als lokale
- Benennung mit 🌐 Präfix um Server-Playlisten zu kennzeichnen
- Settings-Button lädt alle Playlisten mit ihren Songs vom Server
- Zeigt Anzahl importierter Playlisten mit SnackBar
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJzQjUtnvYUtHdnTs3iCru
- Neue NavidromeService APIs: scrobble() + getBookmark()
- Lädt Wiedergabe-Position vom Server beim Song-Start (Subsonic-API)
- Sendet Position alle 5s zum Server wenn verbunden
- Priorisiert Server-Position über lokale Position
- Full Sync: lokal speichern + Server synchen parallel
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJzQjUtnvYUtHdnTs3iCru
IMPLEMENTATION:
- NavidromeService.streamAndCacheToLocal(): Stream + gleichzeitig cachen
- Cache-First-Logik: Wenn Cache existiert → lokal spielen
- AudioHandler erweitert: loadPlaylist() nutzt Auto-Caching
- CacheManager Integration in AudioHandler
FLOW:
1. User spielt Album ab → streamt vom Server
2. Während des Abspielens → lokal in Cache speichern
3. Nächstes Mal: Lokal spielen (schneller, Offline möglich)
Phase 2 P0 COMPLETE! ✅
Tests: 70/70 ✅
LYRICS:
- Lyrics-Button im Now-Playing Screen (📝 Icon)
- Bottom-Sheet mit Lyrics vom Server (getLyrics() API)
- Fallback: 'Keine Lyrics verfügbar'
- Lyrics-Klasse für Datenmodell
SHUFFLE/REPEAT:
- Shuffle-Button (schon implementiert!) zeigt aktiven Status rot
- Repeat-Button (Off → All → One → Off cycle)
- Beide via AudioHandler.setShuffleMode() / setRepeatMode()
- Integration in _Controls (Player-UI)
Phase 1 P1-Features jetzt funktionsfähig!
Tests: 70/70 ✅
- getArtists(): Alle Künstler vom Server laden
- getArtistSongs(): Alle Songs eines Künstlers
- setFavorite()/removeFavorite(): Server-Favoriten toggle
- getFavorites(): Favoriten vom Server laden
- SubsonicArtist-Klasse für Künstler
APIs für Phase 2 vorbereitet (Artist-Tab, Server-Favoriten-Sync)
Tests: 70/70 grün