fix: Login-Fehlerhandler — 200 ohne Token abfangen, Server-Nachrichten parsen

This commit is contained in:
Dustin
2026-08-01 17:44:05 +02:00
parent 7f17a5afd6
commit a495a183d8
6 changed files with 122 additions and 7 deletions
+5 -2
View File
@@ -76,13 +76,16 @@ class AuthService {
MeloLogger().aktion('auth_login_ok', {'user': _user});
return AuthResult.ok;
}
// 200 aber kein Token — Server-Fehler
MeloLogger().fehler('auth_login_no_token', response.body);
return AuthResult.fehlgeschlagen('Server-Antwort ohne Token');
}
// Fehler vom Server parsen
// Nicht-200: Fehler vom Server parsen
String fehler = 'Unbekannter Fehler';
try {
final data = jsonDecode(response.body);
fehler = data['error'] as String? ?? 'Login fehlgeschlagen (${response.statusCode})';
fehler = data['error'] as String? ?? data['message'] as String? ?? 'Login fehlgeschlagen (${response.statusCode})';
} catch (_) {
fehler = 'Server nicht erreichbar (${response.statusCode})';
}
+30
View File
@@ -318,6 +318,7 @@ class DownloadService extends ChangeNotifier {
istHeruntergeladen: true,
istKorrupt: istKorrupt,
downloadQuelle: 'youtube',
ytUrl: url,
);
await _db.songEinfuegen(song);
@@ -426,6 +427,35 @@ class DownloadService extends ChangeNotifier {
}
}
/// Korrupten Song mit bekannter ytUrl neu herunterladen
Future<Song?> reDownload(Song song) async {
if (song.ytUrl == null || song.ytUrl!.isEmpty) {
_fehler = 'Keine YouTube-Quell-URL für diesen Song';
notifyListeners();
return null;
}
_resetStatus();
_aktuellerTitel = '🔄 Lade neu: ${song.titel}';
notifyListeners();
// Alten Song-Eintrag aus DB löschen
if (song.id != null) {
await _db.loeschSong(song.id!);
}
// Alte Datei löschen
try {
final alteDatei = File(song.dateiPfad);
if (await alteDatei.exists()) {
await alteDatei.delete();
}
} catch (_) {}
// Neu herunterladen mit der gespeicherten ytUrl
return _downloadEinzeln(song.ytUrl!);
}
@override
void dispose() {
abbrechen();
+54
View File
@@ -3,6 +3,8 @@ import 'dart:io';
import 'package:just_audio/just_audio.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import '../models/song.dart';
import '../database/db_helper.dart';
import '../utils/audio_validator.dart';
@@ -107,6 +109,9 @@ class MusikScanner {
_neueSongs = neue.length;
}
// ── YouTube-Quell-URLs für Songs ohne ytUrl nachschlagen ──
await _sucheYtUrls();
return gefunden;
}
@@ -198,4 +203,53 @@ class MusikScanner {
final dot = name.lastIndexOf('.');
return dot > 0 ? name.substring(0, dot) : name;
}
/// Sucht YouTube-Quell-URLs für Songs ohne ytUrl (max 50 pro Scan, 1/s Rate-Limit)
Future<void> _sucheYtUrls() async {
const maxSuchanfragen = 50;
const suchUrlBasis = 'https://yt.baka-net.de/api/search';
final songsOhneUrl = await _db.songsOhneYtUrl(limit: maxSuchanfragen);
if (songsOhneUrl.isEmpty) return;
int gefunden = 0;
for (final song in songsOhneUrl) {
if (gefunden >= maxSuchanfragen) break;
try {
final query = '${song.kuenstler} ${song.titel}';
final url = '$suchUrlBasis?q=${Uri.encodeQueryComponent(query)}';
final antwort = await http.get(Uri.parse(url)).timeout(
const Duration(seconds: 10),
);
if (antwort.statusCode == 200) {
final daten = jsonDecode(antwort.body);
if (daten is List && daten.isNotEmpty) {
final erstes = daten[0];
final ytUrl = erstes['url'] as String?;
if (ytUrl != null && ytUrl.isNotEmpty) {
await _db.ytUrlAktualisieren(song.id!, ytUrl);
gefunden++;
MeloLogger().zustand('yt_suche_treffer', {
'song_id': song.id,
'titel': song.titel,
'yt_url': ytUrl,
});
}
}
}
} catch (e) {
MeloLogger().fehler('yt_suche_fehler', '${song.titel}: $e');
}
// Rate-Limit: 1 Anfrage pro Sekunde
await Future.delayed(const Duration(seconds: 1));
}
if (gefunden > 0) {
MeloLogger().zustand('yt_suche_abschluss', {'gefunden': gefunden});
}
}
}