aves_mio18/lib/remote/remote_controller.dart.old
2026-04-14 09:53:02 +02:00

223 lines
6.9 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:aves/model/source/collection_source.dart';
import 'package:aves/remote/remote_settings.dart';
import 'package:aves/remote/remote_sync_bus.dart';
import 'package:aves/remote/remote_repository.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/remote/remote_client.dart';
import 'package:aves/remote/auth_client.dart';
import 'package:aves/remote/collection_source_remote_ext.dart';
import 'remote_origin.dart';
class RemoteController {
RemoteController._();
static final RemoteController instance = RemoteController._();
static const _kBootstrapDone = 'remote_bootstrap_done';
bool _syncInFlight = false;
Future<bool> bootstrapDone() async {
final storage = FlutterSecureStorage();
return (await storage.read(key: _kBootstrapDone)) == '1';
}
Future<void> _setBootstrapDone() async {
final storage = FlutterSecureStorage();
await storage.write(key: _kBootstrapDone, value: '1');
}
/// Chiamare allavvio: imposta lo stato icona (grigio/verde) coerente con settings.
Future<void> initBusFromSettings() async {
final s = await RemoteSettings.load();
if (!s.enabled) {
RemoteSyncBus.instance.setDisabled();
} else {
// enabled: stato iniziale "upToDate" (poi la sync può cambiare)
final opId = RemoteSyncBus.instance.start(total: 0, showOverlay: false);
RemoteSyncBus.instance.finishUpToDate(opId: opId);
}
}
/// Logica davvio app:
/// - se remote OFF -> nascondi remoti dalla UI (memoria only) e stop
/// - se remote ON:
/// - se bootstrap done -> append DB immediato + sync silenzioso
/// - se bootstrap NOT done -> opzionale resume bootstrap
Future<void> onAppStart({
required CollectionSource source,
bool resumeBootstrapIfEnabled = true,
}) async {
final s = await RemoteSettings.load();
if (!s.enabled) {
RemoteSyncBus.instance.setDisabled();
final remotesInMemory = source.allEntries.where((e) => e.origin == RemoteOrigin.value).toSet();
if (remotesInMemory.isNotEmpty) {
source.removeEntriesFromMemory(remotesInMemory);
}
return;
}
final done = await bootstrapDone();
if (done) {
await source.appendRemoteEntriesFromDb();
// sync in background (solo icona)
unawaited(fullSync(source: source, showOverlay: false));
} else {
if (resumeBootstrapIfEnabled) {
unawaited(fullSync(
source: source,
showOverlay: true,
markBootstrapDoneOnSuccess: true,
));
}
}
}
/// Toggle da icona (tap)
Future<void> toggleRemote({required CollectionSource source}) async {
final s = await RemoteSettings.load();
if (s.enabled) {
// TURN OFF
final upd = RemoteSettings(
enabled: false,
baseUrl: s.baseUrl,
indexPath: s.indexPath,
email: s.email,
password: s.password,
);
await upd.save();
debugPrint('[remote] toggle -> enabled=false (OFF)');
// nascondi remoti (memoria only)
final remotesInMemory = source.allEntries.where((e) => e.origin == RemoteOrigin.value).toSet();
if (remotesInMemory.isNotEmpty) {
source.removeEntriesFromMemory(remotesInMemory);
}
// invalida sync in corso e icona grigia
RemoteSyncBus.instance.setDisabled();
debugPrint('[remote] toggled OFF -> removed remotes from memory=${remotesInMemory.length}');
return;
}
// TURN ON
final upd = RemoteSettings(
enabled: true,
baseUrl: s.baseUrl,
indexPath: s.indexPath,
email: s.email,
password: s.password,
);
await upd.save();
debugPrint('[remote] toggle -> enabled=true (ON)');
final first = !(await bootstrapDone());
if (first) {
debugPrint('[remote] first enable -> FULL sync with overlay');
await fullSync(
source: source,
showOverlay: true,
markBootstrapDoneOnSuccess: true,
);
return;
}
debugPrint('[remote] enable -> append DB then background sync');
await source.appendRemoteEntriesFromDb();
unawaited(fullSync(source: source, showOverlay: false));
}
/// Full sync remoto:
/// - showOverlay=true solo bootstrap
/// - showOverlay=false -> solo icona
Future<void> fullSync({
required CollectionSource source,
required bool showOverlay,
bool markBootstrapDoneOnSuccess = false,
}) async {
if (_syncInFlight) {
debugPrint('[remote] sync skipped (already in flight)');
return;
}
_syncInFlight = true;
final s = await RemoteSettings.load();
if (!s.enabled) {
RemoteSyncBus.instance.setDisabled();
_syncInFlight = false;
return;
}
// Start: token opId (protezione anti-race)
final opId = RemoteSyncBus.instance.start(total: 0, showOverlay: showOverlay);
try {
// base URL vuota -> server down
if (s.baseUrl.trim().isEmpty) {
debugPrint('[remote] serverDown (empty baseUrl)');
RemoteSyncBus.instance.failServerDown(opId: opId);
return;
}
RemoteAuth? auth;
if (s.email.isNotEmpty && s.password.isNotEmpty) {
auth = RemoteAuth(baseUrl: s.baseUrl, email: s.email, password: s.password);
}
final client = RemoteJsonClient(s.baseUrl, s.indexPath, auth: auth);
// fetch full list
final items = await client.fetchAll().timeout(const Duration(seconds: 30));
final total = items.length;
debugPrint('[remote] sync start overlay=$showOverlay total=$total');
// aggiorna total corretto
RemoteSyncBus.instance.update(opId: opId, done: 0, total: total);
final repo = RemoteRepository(localMediaDb.rawDb);
await repo.deleteAllRemotes();
const chunkSize = 200;
int done = 0;
final serverIds = items.map((e) => e.id).where((v) => v.isNotEmpty).toSet();
for (var offset = 0; offset < total; offset += chunkSize) {
final end = (offset + chunkSize < total) ? offset + chunkSize : total;
await repo.upsertAll(items.sublist(offset, end), chunkSize: chunkSize);
done = end;
RemoteSyncBus.instance.update(opId: opId, done: done, total: total);
}
await repo.pruneMissingRemotes(serverIds);
// mostra remoti in UI (dopo sync)
await source.appendRemoteEntriesFromDb();
debugPrint('[remote] sync done');
if (markBootstrapDoneOnSuccess) {
await _setBootstrapDone();
}
RemoteSyncBus.instance.finishUpToDate(opId: opId);
} on TimeoutException {
debugPrint('[remote] serverDown (timeout)');
RemoteSyncBus.instance.failServerDown(opId: opId);
} catch (e) {
debugPrint('[remote] serverDown (error=$e)');
RemoteSyncBus.instance.failServerDown(opId: opId);
} finally {
_syncInFlight = false;
}
}
}