Gezielter Upload mit Abbrechen aus dem Auswahl-Modus

This commit is contained in:
Hermes (Server)
2026-08-27 11:31:37 +02:00
parent 8d84a88a9c
commit 78968fd6b8
2 changed files with 274 additions and 0 deletions
+125
View File
@@ -122,6 +122,32 @@ const autoSyncAbstand = Duration(minutes: 15);
bool sollAutoSync(DateTime? letzterLauf, DateTime jetzt) =>
letzterLauf == null || jetzt.difference(letzterLauf) >= autoSyncAbstand;
/// Was ein gezielter Upload erledigt hat — Grundlage der Meldung an den
/// Nutzer. Ein einzelner Fehlschlag darf den Erfolg der übrigen nicht
/// verdecken, deshalb steht hier alles nebeneinander.
class UploadErgebnis {
const UploadErgebnis({
this.hochgeladen = 0,
this.schonDa = 0,
this.fehler = const [],
this.abgebrochen = false,
});
final int hochgeladen;
final int schonDa;
final List<String> fehler;
final bool abgebrochen;
String get meldung {
final teile = <String>[];
if (hochgeladen > 0) teile.add('$hochgeladen hochgeladen');
if (schonDa > 0) teile.add('$schonDa waren schon da');
if (fehler.isNotEmpty) teile.add('${fehler.length} fehlgeschlagen');
if (abgebrochen) teile.add('abgebrochen');
return teile.isEmpty ? 'Nichts zu tun' : teile.join(', ');
}
}
/// 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.
@@ -148,6 +174,7 @@ class SyncService extends ChangeNotifier {
String? _fehler;
String? _status;
DateTime? _letzterLauf;
bool _abbruchGewuenscht = false;
bool get laeuft => _laeuft;
int get erledigt => _erledigt;
@@ -230,6 +257,104 @@ class SyncService extends ChangeNotifier {
}
}
/// Stoppt einen laufenden Auswahl-Upload zwischen zwei Titeln.
///
/// Nach dem Muster von `DownloadService.abbrechen`: wer versehentlich 60
/// statt 6 Titel markiert hat, soll nicht die App killen müssen.
void abbrechen() {
if (_laeuft) _abbruchGewuenscht = true;
}
/// Lädt genau [songs] zum Server — die ausdrückliche Nutzeraktion aus dem
/// Auswahl-Modus.
///
/// Sequenziell, weil der Server jeden Upload komplett im RAM hält. Titel mit
/// cloudId werden übersprungen, Einzelfehler vermerkt und übergangen.
/// Der Sync-Zeitstempel wird bewusst **nicht** geschrieben: ein Upload ist
/// kein Abgleich, und sonst unterdrückte er 15 Minuten den Auto-Sync und
/// verschöbe die 24-h-Uhr des Berichts.
///
/// Den Offline-Modus-Schalter beachtet er nicht — er ist eine ausdrückliche
/// Nutzeraktion.
Future<UploadErgebnis> ladeAusgewaehlteHoch(List<Song> songs) async {
if (_laeuft) {
_fehler = 'Es läuft gerade ein Abgleich — bitte kurz warten';
notifyListeners();
return const UploadErgebnis();
}
if (!cloud.istAngemeldet) {
_fehler = 'Bitte zuerst beim Baka-Konto anmelden';
notifyListeners();
return const UploadErgebnis();
}
_laeuft = true;
_abbruchGewuenscht = false;
_fehler = null;
_erledigt = 0;
_gesamt = songs.length;
notifyListeners();
var hochgeladen = 0;
var schonDa = 0;
final fehler = <String>[];
var abgebrochen = false;
try {
for (final song in songs) {
if (_abbruchGewuenscht) {
abgebrochen = true;
break;
}
if (song.cloudId != null) {
schonDa++;
_erledigt++;
notifyListeners();
continue;
}
final datei = File(song.path);
if (!await datei.exists()) {
fehler.add('${song.title}: Datei nicht gefunden');
_erledigt++;
notifyListeners();
continue;
}
_melde('Sende „${song.title}" …');
try {
final cloudId = await cloud.hochladen(
datei,
dateiname:
'${_sichererDateiname(song.title)}${p.extension(song.path)}',
);
if (cloudId != null) {
await db.setCloudId(song.id, cloudId);
hochgeladen++;
} else {
fehler.add('${song.title}: keine Server-ID erhalten');
}
} on CloudException catch (e) {
fehler.add('${song.title}: ${e.message}');
} on TimeoutException {
fehler.add('${song.title}: Zeitüberschreitung');
}
_erledigt++;
notifyListeners();
}
} finally {
_laeuft = false;
_abbruchGewuenscht = false;
_status = null;
notifyListeners();
}
return UploadErgebnis(
hochgeladen: hochgeladen,
schonDa: schonDa,
fehler: fehler,
abgebrochen: abgebrochen,
);
}
Future<void> _ziehLoeschungenNach(List<Song> songs) async {
if (songs.isEmpty) return;
_melde('Entferne ${songs.length} am Server gelöschte Titel …');