v2.52.1 — F2: YT-Links nur als Server-Fallback

## YT-Quell-URL (Server ist die Quelle)
- cloud_service.ytUrlSetzen(): POST /api/v1/cloud/yt-url (per-User UPDATE user_songs.yt_url)
- musik_scanner._sucheYtUrls(): durchsucht NUR Songs mit cloud_id, die auf dem Server noch keine yt_url haben; Treffer werden auf den Server hochgeladen statt lokal gespeichert
- db_helper: ytUrlAktualisieren/songsOhneYtUrl entfernt, songsMitCloudId() ergänzt
- download_service: yt_url wird beim Download nicht mehr lokal persistiert

## Fallback-Kette reDownload (korrupte/fehlende Datei)
- 1) Server-Download via cloud_id + Magic-Byte-Check (primäre Quelle)
- 2) Server-ytUrl (aus listSongs) → yt-proxy als letzte Chance
- 3) Legacy: lokal gespeicherte ytUrl (Bestandsdaten vor v2.52)
- song_tile: „Neu laden“ erscheint bei istKorrupt && (cloudId != null || ytUrl != null)
- 3 neue Tests (ytUrlSetzen: ok/error/401) → 111 Tests grün, analyze 0 Issues
This commit is contained in:
Dustin
2026-08-05 10:40:37 +02:00
parent 3b898e238e
commit bf0f544e4b
6 changed files with 158 additions and 35 deletions
+73 -10
View File
@@ -11,6 +11,7 @@ import '../database/db_helper.dart';
import '../utils/audio_validator.dart';
import 'melo_logger.dart';
import 'auth_service.dart';
import 'cloud_service.dart';
import '../main.dart'; // für notificationsPlugin
import '../utils/sanitize.dart';
@@ -370,7 +371,8 @@ class DownloadService extends ChangeNotifier {
istHeruntergeladen: true,
istKorrupt: istKorrupt,
downloadQuelle: 'youtube',
ytUrl: url,
// F2: yt_url wird NICHT mehr lokal persistiert — der Server ist die
// Quelle (CloudService.ytUrlSetzen beim Scanner; Fallback via listSongs).
);
await _db.songEinfuegen(song);
@@ -479,14 +481,11 @@ class DownloadService extends ChangeNotifier {
}
}
/// Korrupten Song mit bekannter ytUrl neu herunterladen
/// Korrupten Song neu herunterladen — Server ist die primäre Quelle (F2):
/// 1. Server-Download (cloud_id) mit Magic-Byte-Check
/// 2. Fallback: Server-ytUrl (aus listSongs) → yt-proxy als letzte Chance
/// 3. Legacy-Fallback: lokal gespeicherte ytUrl (Bestandsdaten vor v2.52)
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();
try {
_aktuellerTitel = '🔄 Lade neu: ${song.titel}';
@@ -505,8 +504,72 @@ class DownloadService extends ChangeNotifier {
}
} catch (_) {}
// Neu herunterladen mit der gespeicherten ytUrl
return _downloadEinzeln(song.ytUrl!);
// 1) Server-Download versuchen (primäre Quelle)
final cloudId = song.cloudId;
if (cloudId != null && cloudId.isNotEmpty) {
final cloud = CloudService();
final dir = Directory(
'${(await getApplicationDocumentsDirectory()).path}/music');
if (!await dir.exists()) await dir.create(recursive: true);
final ext = song.dateiPfad.contains('.')
? '.${song.dateiPfad.split('.').last}'
: '.mp3';
final dest = '${dir.path}/${sanitizeDateiname(song.titel)}$ext';
if (await cloud.download(cloudId, dest)) {
final file = File(dest);
if (hatValideMagicBytes(file)) {
final neu = Song(
titel: song.titel,
kuenstler: song.kuenstler,
album: song.album,
dauerSekunden: song.dauerSekunden,
dateiPfad: dest,
groesseBytes: await file.length(),
istHeruntergeladen: true,
istKorrupt: false,
downloadQuelle: 'server',
cloudId: cloudId,
);
await _db.songEinfuegen(neu);
_ladt = false;
_aktuellerTitel = '${neu.titel}';
notifyListeners();
return neu;
}
// Server-Kopie korrupt → löschen, Fallback versuchen
try {
await file.delete();
} catch (_) {}
}
// 2) Server-ytUrl als letzte Chance (Server-Datei fehlt oder korrupt)
final serverSongs = await cloud.listSongs();
for (final s in serverSongs) {
if ((s['id']?.toString() ?? '') == cloudId) {
final serverYt = s['ytUrl']?.toString() ?? '';
if (serverYt.isNotEmpty) {
return _downloadEinzeln(serverYt);
}
break;
}
}
// 3) Legacy: lokal gespeicherte ytUrl (Bestandsdaten vor v2.52)
if (song.ytUrl != null && song.ytUrl!.isNotEmpty) {
return _downloadEinzeln(song.ytUrl!);
}
_fehler = 'Kein Server-Download möglich und keine YouTube-Quelle';
notifyListeners();
return null;
}
// Kein cloudId → nur Legacy-ytUrl
if (song.ytUrl != null && song.ytUrl!.isNotEmpty) {
return _downloadEinzeln(song.ytUrl!);
}
_fehler = 'Keine YouTube-Quell-URL für diesen Song';
notifyListeners();
return null;
} finally {
_stopBackgroundService();
}