v2.49.4 — Favoriten-Toggle race-sicher (kein PK-Crash bei Doppel-Tap)

## Fix (Code-Review Finding 2, HIGH)
- DbHelper.songZurPlaylist: INSERT OR IGNORE (ConflictAlgorithm.ignore) —
  Doppel-Tap-Race verletzt PRIMARY KEY (playlist_id, song_id) nicht mehr
- FavoritenService.umschalten: gibt neuen Status zurück, try/catch mit
  MeloLogger, Zustand nach Fehler aus DB neu geladen statt blind geflippt
- NowPlayingScreen._toggleFavorit: In-Flight-Guard _toggleLaeuft gegen
  parallele Toggles + Statusübernahme aus DB (Quelle der Wahrheit)
This commit is contained in:
Dustin
2026-08-04 16:46:49 +02:00
parent c6826db651
commit bd692d4682
3 changed files with 41 additions and 10 deletions
+22 -7
View File
@@ -1,5 +1,7 @@
import 'package:flutter/foundation.dart';
import '../database/db_helper.dart';
import '../models/song.dart';
import 'melo_logger.dart';
class FavoritenService {
static final FavoritenService _instanz = FavoritenService._();
@@ -27,13 +29,26 @@ class FavoritenService {
return songs.any((s) => s.id == songId);
}
Future<void> umschalten(int songId) async {
if (_favoritenPlaylistId == null) return;
if (await istFavorit(songId)) {
await _db.songAusPlaylistEntfernen(_favoritenPlaylistId!, songId);
} else {
final songs = await _db.songsDerPlaylist(_favoritenPlaylistId!);
await _db.songZurPlaylist(_favoritenPlaylistId!, songId, songs.length);
/// Schaltet den Favoriten-Status um und gibt den NEUEN Status zurück
/// (true = jetzt Favorit). Bei Fehlern (z. B. DB-Race) wird der Zustand
/// aus der DB neu geladen statt blind geflippt — kein unhandled Throw.
Future<bool> umschalten(int songId) async {
if (_favoritenPlaylistId == null) return false;
try {
if (await istFavorit(songId)) {
await _db.songAusPlaylistEntfernen(_favoritenPlaylistId!, songId);
return false;
} else {
final songs = await _db.songsDerPlaylist(_favoritenPlaylistId!);
// INSERT OR IGNORE in songZurPlaylist fängt Doppel-Tap-Races ab
await _db.songZurPlaylist(_favoritenPlaylistId!, songId, songs.length);
return true;
}
} catch (e) {
debugPrint('Favoriten-Toggle fehlgeschlagen: $e');
MeloLogger().fehler('favoriten_toggle', e);
// Zustand nach Fehler aus der DB synchronisieren (Quelle der Wahrheit)
return istFavorit(songId);
}
}