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

294 lines
7.8 KiB
Text
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. 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;
// 🔥 Retry esterno
Timer? _retryTimer;
int _retryCount = 0;
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');
}
// 🔥 Stato iniziale corretto: arancione lampeggiante
Future<void> initBusFromSettings() async {
final s = await RemoteSettings.load();
if (!s.enabled) {
RemoteSyncBus.instance.setDisabled();
return;
}
RemoteSyncBus.instance.stateNotifier.value = RemoteSyncState.syncing;
}
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();
unawaited(fullSync(source: source, showOverlay: false));
} else {
if (resumeBootstrapIfEnabled) {
unawaited(fullSync(
source: source,
showOverlay: true,
markBootstrapDoneOnSuccess: true,
));
}
}
}
Future<void> toggleRemote({required CollectionSource source}) async {
final s = await RemoteSettings.load();
if (s.enabled) {
final upd = RemoteSettings(
enabled: false,
baseUrl: s.baseUrl,
indexPath: s.indexPath,
email: s.email,
password: s.password,
);
await upd.save();
debugPrint('[remote] toggle -> OFF');
final remotesInMemory =
source.allEntries.where((e) => e.origin == RemoteOrigin.value).toSet();
if (remotesInMemory.isNotEmpty) {
source.removeEntriesFromMemory(remotesInMemory);
}
RemoteSyncBus.instance.setDisabled();
// 🔥 Stop retry loop
_retryTimer?.cancel();
_retryTimer = null;
return;
}
final upd = RemoteSettings(
enabled: true,
baseUrl: s.baseUrl,
indexPath: s.indexPath,
email: s.email,
password: s.password,
);
await upd.save();
debugPrint('[remote] toggle -> ON');
await source.appendRemoteEntriesFromDb();
unawaited(fullSync(source: source, showOverlay: false));
}
// 🔥 Retry esterno
void _startRetryLoop(CollectionSource source) {
_retryTimer?.cancel();
_retryCount = 0;
_retryTimer = Timer.periodic(const Duration(seconds: 30), (timer) async {
_retryCount++;
print('[remote] retry $_retryCount/10');
final s = await RemoteSettings.load();
if (!s.enabled) {
timer.cancel();
return;
}
final auth = (s.email.isNotEmpty && s.password.isNotEmpty)
? RemoteAuth(
baseUrl: s.baseUrl,
email: s.email,
password: s.password,
)
: null;
final retryClient = RemoteJsonClient(
s.baseUrl,
s.indexPath,
auth: auth,
);
try {
await retryClient.ping().timeout(const Duration(seconds: 3));
print('[remote] retry OK → riprendo sync');
timer.cancel();
_retryTimer = null;
unawaited(fullSync(source: source, showOverlay: false));
return;
} catch (_) {
print('[remote] retry fallito');
}
if (_retryCount >= 10) {
print('[remote] server down per troppo tempo → disattivo remote');
timer.cancel();
_retryTimer = null;
final remotesInMemory =
source.allEntries.where((e) => e.origin == RemoteOrigin.value).toSet();
if (remotesInMemory.isNotEmpty) {
source.removeEntriesFromMemory(remotesInMemory);
}
final upd = RemoteSettings(
enabled: false,
baseUrl: s.baseUrl,
indexPath: s.indexPath,
email: s.email,
password: s.password,
);
await upd.save();
RemoteSyncBus.instance.setDisabled();
}
});
}
// 🔥 fullSync pulito, senza retry interno
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;
}
try {
if (s.baseUrl.trim().isEmpty) {
RemoteSyncBus.instance.stateNotifier.value = RemoteSyncState.serverDown;
_startRetryLoop(source);
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);
// 1⃣ PING
await client.ping().timeout(const Duration(seconds: 3));
// 2⃣ FETCH LISTA
final items = await client.fetchAll().timeout(const Duration(seconds: 30));
final total = items.length;
// 3⃣ BANNER
final opId =
RemoteSyncBus.instance.start(total: total, showOverlay: showOverlay);
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);
await source.appendRemoteEntriesFromDb();
if (markBootstrapDoneOnSuccess) {
await _setBootstrapDone();
}
RemoteSyncBus.instance.finishUpToDate(opId: opId);
// 🔥 Se la sync va bene → stop retry
_retryTimer?.cancel();
_retryTimer = null;
} catch (e) {
print('[remote] error during sync: $e');
final remotesInMemory =
source.allEntries.where((e) => e.origin == RemoteOrigin.value).toSet();
if (remotesInMemory.isNotEmpty) {
source.removeEntriesFromMemory(remotesInMemory);
}
RemoteSyncBus.instance.stateNotifier.value = RemoteSyncState.serverDown;
// 🔥 Avvia retry esterno
_startRetryLoop(source);
} finally {
_syncInFlight = false;
}
}
}