import 'drive_service.dart' show DriveLockFile; /// Ticket-based mutex using timestamped lock files as the coordination /// point: create a lock named after our own timestamp, then wait until no /// *other* lock older than ours remains — reaping ("deleting") any it /// finds older than [staleAge] along the way, as a backstop against a /// device that crashed/went offline before releasing its own lock. /// /// Pure orchestration over injected operations (rather than a concrete /// Drive dependency) so the state machine is unit-testable without a real /// network connection. Future acquireLock({ required String username, required Future Function(String lockName) createLock, required Future> Function() listLocks, required Future Function(String lockId) deleteLock, DateTime Function()? nowUtc, Future Function(Duration)? delay, Duration staleAge = const Duration(minutes: 10), Duration pollInterval = const Duration(seconds: 1), }) async { final now = nowUtc ?? () => DateTime.now().toUtc(); final wait = delay ?? Future.delayed; final epochMillis = now().millisecondsSinceEpoch; final lockName = '$username-$epochMillis.lock'; final lockId = await createLock(lockName); while (true) { final locks = await listLocks(); final others = locks.where((l) => l.id != lockId); final olderOthers = others.where((l) => l.createdAtUtc.millisecondsSinceEpoch < epochMillis).toList(); if (olderOthers.isEmpty) break; final current = now(); for (final stale in olderOthers) { if (current.difference(stale.createdAtUtc) > staleAge) { await deleteLock(stale.id); } } await wait(pollInterval); } return lockId; }