Favoriten additiv abgleichen statt ersetzen; Upload-Timeout ist Einzelfehler

This commit is contained in:
Hermes (Server)
2026-08-27 10:55:00 +02:00
parent 09d24abf4d
commit 5dc80931e2
3 changed files with 246 additions and 26 deletions
-13
View File
@@ -293,19 +293,6 @@ class MeloCloudService {
_pruefeStatus(antwort);
}
/// Ersetzt die Favoriten am Server durch [cloudIds].
Future<void> setzeFavoriten(List<String> cloudIds) async {
_pruefeAnmeldung();
final antwort = await _client
.post(
Uri.parse('$basisUrl/favorites'),
headers: {..._kopf, 'Content-Type': 'application/json'},
body: jsonEncode({'song_ids': cloudIds}),
)
.timeout(const Duration(seconds: 30));
_pruefeStatus(antwort);
}
/// Meldet Wiedergaben. Der Server nimmt höchstens 100 je Aufruf an und
/// verwirft Doppelmeldungen desselben Titels innerhalb einer Stunde.
Future<void> meldeVerlauf(List<CloudVerlauf> eintraege) async {
+62 -8
View File
@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:io';
import 'package:drift/drift.dart' show Value;
@@ -9,6 +10,7 @@ import 'package:uuid/uuid.dart';
import '../library/database.dart';
import 'media_store.dart';
import 'melo_cloud_service.dart';
import 'sync_merge.dart';
/// Was beim Abgleich mit welchen Titeln zu tun ist.
///
@@ -313,25 +315,77 @@ class SyncService extends ChangeNotifier {
} on CloudException catch (e) {
// Eine zu große oder abgelehnte Datei darf den Lauf nicht beenden.
debugPrint('Upload „${song.title}“ übersprungen: ${e.message}');
} on TimeoutException {
// Der 120-s-Timeout (melo_cloud_service.dart:180) wirft
// TimeoutException, nicht CloudException — ohne diesen Zweig riss ein
// einziger hängender Upload den ganzen Lauf ab.
debugPrint('Upload „${song.title}“: Zeitüberschreitung');
}
_erledigt++;
notifyListeners();
}
}
Future<void> _gleicheFavoritenAb() async {
/// Additiver Favoriten-Abgleich: gleicht in **beide** Richtungen an,
/// entfernt aber in **keiner**.
///
/// Damit ist der alte Datenverlust-Bug strukturell unmöglich: es gibt
/// keinen Codepfad mehr, der den Server-Stand ersetzen könnte. Der Preis
/// ist bekannt und bewusst: ein Ent-Favorisieren propagiert nicht
/// geräteübergreifend — hält ein zweites Gerät den Favoriten noch, bringt
/// dessen nächster Abgleich ihn zurück.
///
/// Gibt `true` zurück, wenn die Phase vollständig durchlief.
Future<bool> _gleicheFavoritenAb() async {
_melde('Gleiche Favoriten ab …');
final Set<String> amServer;
try {
amServer = (await cloud.favoriten()).toSet();
} catch (e) {
// Ohne Server-Stand ist nichts zu tun. Blindes Pushen wäre harmlos,
// aber nutzlos — die Phase wird übersprungen, der Lauf geht weiter.
debugPrint('Favoriten-Abgleich übersprungen: $e');
return false;
}
// Grabsteine bleiben außen vor: favoriteSongIds() liefert auch Favoriten
// getombsteter Titel, und die gehören nicht zurück auf den Server.
final lokal = await db.allSongs();
final cloudIdVon = {
for (final s in lokal)
if (s.cloudId != null) s.id: s.cloudId!,
if (s.cloudId != null && !s.deleted) s.id: s.cloudId!,
};
final favoritenIds = await db.favoriteSongIds();
final cloudFavoriten = [
for (final id in favoritenIds)
if (cloudIdVon[id] != null) cloudIdVon[id]!,
];
await cloud.setzeFavoriten(cloudFavoriten);
final songIdVonCloudId = {
for (final s in lokal)
if (s.cloudId != null && !s.deleted) s.cloudId!: s.id,
};
final favoriten = (await db.favoriteSongIds()).toSet();
var vollstaendig = true;
for (final cloudId in zuPushendeFavoriten(
lokaleFavoriten: favoriten,
cloudIdVon: cloudIdVon,
amServer: amServer,
)) {
try {
await cloud.setzeFavorit(cloudId, true);
} catch (e) {
debugPrint('Favorit $cloudId nicht gemeldet: $e');
vollstaendig = false;
}
}
for (final songId in lokalZuSetzendeFavoriten(
amServer: amServer,
songIdVonCloudId: songIdVonCloudId,
lokaleFavoriten: favoriten,
)) {
await db.setFavorite(songId, true);
}
return vollstaendig;
}
Future<void> _meldeVerlauf() async {