Erfolgs-Flag, Zeitstempel-Snapshot und Zaehler fuer den Sync-Bericht
This commit is contained in:
@@ -148,6 +148,22 @@ class UploadErgebnis {
|
||||
}
|
||||
}
|
||||
|
||||
/// Was seit dem letzten erfolgreichen Abgleich passiert ist — der Inhalt des
|
||||
/// „Willkommen zurück"-Dialogs.
|
||||
class SyncBericht {
|
||||
const SyncBericht({
|
||||
required this.neueSongs,
|
||||
required this.geloeschte,
|
||||
required this.favoriten,
|
||||
});
|
||||
|
||||
final int neueSongs;
|
||||
final int geloeschte;
|
||||
final int favoriten;
|
||||
|
||||
bool get istLeer => neueSongs == 0 && geloeschte == 0 && favoriten == 0;
|
||||
}
|
||||
|
||||
/// Hält Gerät und Server auf demselben Stand: lädt neue Server-Titel herunter,
|
||||
/// bringt eigene Dateien hoch, zieht Löschungen nach und meldet Favoriten und
|
||||
/// Wiedergaben. Alle Geräte am selben Konto sehen dadurch dasselbe.
|
||||
@@ -160,6 +176,7 @@ class SyncService extends ChangeNotifier {
|
||||
}) : _musikOrdner = musikOrdner ?? _standardMusikOrdner;
|
||||
|
||||
static const _letzterLaufKey = 'cloud_sync_letzter_lauf';
|
||||
static const _letzterErfolgKey = 'cloud_sync_letzter_erfolg';
|
||||
static const _verlaufStandKey = 'cloud_sync_verlauf_stand';
|
||||
static const _uuid = Uuid();
|
||||
|
||||
@@ -174,6 +191,8 @@ class SyncService extends ChangeNotifier {
|
||||
String? _fehler;
|
||||
String? _status;
|
||||
DateTime? _letzterLauf;
|
||||
DateTime? _letzterErfolg;
|
||||
SyncBericht? _bericht;
|
||||
bool _abbruchGewuenscht = false;
|
||||
|
||||
bool get laeuft => _laeuft;
|
||||
@@ -183,10 +202,23 @@ class SyncService extends ChangeNotifier {
|
||||
String? get status => _status;
|
||||
DateTime? get letzterLauf => _letzterLauf;
|
||||
|
||||
/// Der fällige Bericht, oder `null`. Wird von der Oberfläche genau einmal
|
||||
/// abgeholt und dann mit [berichtGesehen] quittiert.
|
||||
SyncBericht? get bericht => _bericht;
|
||||
|
||||
void berichtGesehen() {
|
||||
_bericht = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> laden() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final ms = prefs.getInt(_letzterLaufKey);
|
||||
if (ms != null) _letzterLauf = DateTime.fromMillisecondsSinceEpoch(ms);
|
||||
final erfolg = prefs.getInt(_letzterErfolgKey);
|
||||
if (erfolg != null) {
|
||||
_letzterErfolg = DateTime.fromMillisecondsSinceEpoch(erfolg);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@@ -214,6 +246,14 @@ class SyncService extends ChangeNotifier {
|
||||
_gesamt = 0;
|
||||
_melde('Vergleiche mit dem Server …');
|
||||
|
||||
// Der Zeitstempel ist der Stand VOR dem Listen: was während des Laufs am
|
||||
// Server passiert, muss beim nächsten Mal noch drankommen (v2-Lektion,
|
||||
// Tombstone-Race).
|
||||
final laufBeginn = DateTime.now();
|
||||
var vollstaendig = true;
|
||||
var neueSongs = 0;
|
||||
var geloeschte = 0;
|
||||
|
||||
try {
|
||||
final amServer = await cloud.liste();
|
||||
final plan = planeSync(
|
||||
@@ -233,18 +273,38 @@ class SyncService extends ChangeNotifier {
|
||||
'$bestand Titeln würden am Server gelöscht. Das sieht nach einem '
|
||||
'Fehler aus (z. B. Speicherkarte nicht eingehängt) — es wurde '
|
||||
'nichts gelöscht.';
|
||||
vollstaendig = false;
|
||||
}
|
||||
|
||||
// Jede Phase für sich: fällt eine aus, laufen die übrigen weiter, und
|
||||
// der Erfolgs-Zeitstempel bleibt stehen.
|
||||
geloeschte = plan.lokalLoeschen.length;
|
||||
await _ziehLoeschungenNach(plan.lokalLoeschen);
|
||||
if (!bremse) await _meldeLoeschungen(plan.serverLoeschen);
|
||||
await _ladeHerunter(plan.herunterladen);
|
||||
await _ladeHoch(plan.hochladen);
|
||||
await _gleicheFavoritenAb();
|
||||
if (!bremse && !await _meldeLoeschungen(plan.serverLoeschen)) {
|
||||
vollstaendig = false;
|
||||
}
|
||||
neueSongs = await _ladeHerunter(plan.herunterladen);
|
||||
if (!await _ladeHoch(plan.hochladen)) vollstaendig = false;
|
||||
if (!await _gleicheFavoritenAb()) vollstaendig = false;
|
||||
await _meldeVerlauf();
|
||||
|
||||
_letzterLauf = DateTime.now();
|
||||
_letzterLauf = laufBeginn;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setInt(_letzterLaufKey, _letzterLauf!.millisecondsSinceEpoch);
|
||||
await prefs.setInt(_letzterLaufKey, laufBeginn.millisecondsSinceEpoch);
|
||||
|
||||
if (vollstaendig) {
|
||||
// „Erfolgreich" heißt: keine Phase hat einen Fehler geschluckt.
|
||||
if (berichtFaellig(_letzterErfolg, laufBeginn)) {
|
||||
_bericht = SyncBericht(
|
||||
neueSongs: neueSongs,
|
||||
geloeschte: geloeschte,
|
||||
favoriten: 0,
|
||||
);
|
||||
}
|
||||
_letzterErfolg = laufBeginn;
|
||||
await prefs.setInt(
|
||||
_letzterErfolgKey, laufBeginn.millisecondsSinceEpoch);
|
||||
}
|
||||
} on CloudException catch (e) {
|
||||
_fehler = e.message;
|
||||
} catch (e) {
|
||||
@@ -363,7 +423,13 @@ class SyncService extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _meldeLoeschungen(List<Song> songs) async {
|
||||
/// Meldet die hier getombsteten Titel am Server.
|
||||
///
|
||||
/// Gibt `false` zurück, sobald eine Meldung geschluckt wurde: der Lauf geht
|
||||
/// weiter, gilt aber nicht mehr als erfolgreich — sonst rückte der
|
||||
/// 24-h-Zeitstempel des Berichts vor, obwohl eine Phase ausgefallen ist.
|
||||
Future<bool> _meldeLoeschungen(List<Song> songs) async {
|
||||
var vollstaendig = true;
|
||||
for (final song in songs) {
|
||||
_melde('Melde Löschung von „${song.title}“ …');
|
||||
try {
|
||||
@@ -371,14 +437,19 @@ class SyncService extends ChangeNotifier {
|
||||
} on CloudException catch (e) {
|
||||
// Eine abgelehnte Löschung darf den Lauf nicht beenden.
|
||||
debugPrint('Löschung „${song.title}“ übersprungen: ${e.message}');
|
||||
vollstaendig = false;
|
||||
}
|
||||
_erledigt++;
|
||||
notifyListeners();
|
||||
}
|
||||
return vollstaendig;
|
||||
}
|
||||
|
||||
Future<void> _ladeHerunter(List<CloudSong> songs) async {
|
||||
if (songs.isEmpty) return;
|
||||
/// Gibt zurück, wie viele Titel wirklich neu dazugekommen sind — die Zahl
|
||||
/// im „Was ist neu"-Bericht.
|
||||
Future<int> _ladeHerunter(List<CloudSong> songs) async {
|
||||
if (songs.isEmpty) return 0;
|
||||
var neu = 0;
|
||||
final ordner = await _musikOrdner();
|
||||
for (final cloudSong in songs) {
|
||||
_melde('Lade „${cloudSong.titel}“ …');
|
||||
@@ -418,12 +489,21 @@ class SyncService extends ChangeNotifier {
|
||||
cloudId: Value(cloudSong.id),
|
||||
),
|
||||
]);
|
||||
neu++;
|
||||
_erledigt++;
|
||||
notifyListeners();
|
||||
}
|
||||
return neu;
|
||||
}
|
||||
|
||||
Future<void> _ladeHoch(List<Song> songs) async {
|
||||
/// Lädt alle Titel ohne cloudId hoch.
|
||||
///
|
||||
/// Gibt `false` zurück, sobald ein Upload an einem Fehler oder einer
|
||||
/// Zeitüberschreitung hängenblieb. Eine **fehlende Datei** zählt bewusst
|
||||
/// nicht dazu: dort ist nichts schiefgegangen, der Titel ist weg und der
|
||||
/// nächste Scan tombstoned ihn.
|
||||
Future<bool> _ladeHoch(List<Song> songs) async {
|
||||
var vollstaendig = true;
|
||||
for (final song in songs) {
|
||||
final datei = File(song.path);
|
||||
if (!await datei.exists()) {
|
||||
@@ -436,19 +516,27 @@ class SyncService extends ChangeNotifier {
|
||||
datei,
|
||||
dateiname: '${_sichererDateiname(song.title)}${p.extension(song.path)}',
|
||||
);
|
||||
if (cloudId != null) await db.setCloudId(song.id, cloudId);
|
||||
if (cloudId != null) {
|
||||
await db.setCloudId(song.id, cloudId);
|
||||
} else {
|
||||
// 200 ohne Server-ID: der Titel ist oben nicht angekommen.
|
||||
vollstaendig = false;
|
||||
}
|
||||
} on CloudException catch (e) {
|
||||
// Eine zu große oder abgelehnte Datei darf den Lauf nicht beenden.
|
||||
debugPrint('Upload „${song.title}“ übersprungen: ${e.message}');
|
||||
vollstaendig = false;
|
||||
} 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');
|
||||
vollstaendig = false;
|
||||
}
|
||||
_erledigt++;
|
||||
notifyListeners();
|
||||
}
|
||||
return vollstaendig;
|
||||
}
|
||||
|
||||
/// Additiver Favoriten-Abgleich: gleicht in **beide** Richtungen an,
|
||||
|
||||
Reference in New Issue
Block a user