import 'dart:developer'; import 'dart:io'; import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:localstore/localstore.dart'; import '../api/request_cache.dart'; import '../session/account_codec.dart'; import 'hydrated_storage_bootstrap.dart'; /// Per-account local data: every account gets its own HydratedBloc box and /// Localstore cache collection, so switching back is instant and offline. /// Only the app settings live in the shared [global] box. abstract final class AccountStorage { static const _legacyCollection = 'MarianumMobile'; static const _settingsKey = 'SettingsCubit'; static late String _baseDir; static final Map _open = {}; static Storage _global = InMemoryStorage(); static Storage get global => _global; /// Opens the global box. Settings of installs from before multi-account /// support are copied over from the legacy box once. static Future init(String baseDir) async { _baseDir = baseDir; _global = await buildHydratedStorageWithFallback( _ensureDir('$baseDir/hydrated_global'), ); HydratedBloc.storage = InMemoryStorage(); if (_global.read(_settingsKey) != null) return; final legacySettings = (await _storageFor('')).read(_settingsKey); if (legacySettings != null) { await _global.write(_settingsKey, legacySettings); } } /// Points HydratedBloc and the request cache at [account]'s data. Blocs /// keep the storage they were created with, so the per-account blocs have /// to be recreated afterwards. static Future activate(AccountEntry? account) async { if (account == null) { HydratedBloc.storage = InMemoryStorage(); RequestCache.collection = _legacyCollection; return; } HydratedBloc.storage = await _storageFor(account.namespace); RequestCache.collection = cacheCollection(account.namespace); } /// Removes all local data of the account with [namespace]. static Future delete(String namespace) async { try { final storage = await _storageFor(namespace); await storage.clear(); // Closed boxes ignore writes, so blocs of the account that are still // mounted until the remount cannot leave data behind. await storage.close(); _open.remove(namespace); if (namespace.isNotEmpty) { final dir = Directory(_dirFor(namespace)); if (dir.existsSync()) await dir.delete(recursive: true); } await Localstore.instance.collection(cacheCollection(namespace)).delete(); } on Object catch (e, s) { log('Account storage delete failed: $e', stackTrace: s); } } static String cacheCollection(String namespace) => namespace.isEmpty ? _legacyCollection : '$_legacyCollection-$namespace'; static Future _storageFor(String namespace) async => _open[namespace] ??= await buildHydratedStorageWithFallback( _ensureDir(_dirFor(namespace)), ); // The legacy account keeps the box at the root of the base directory. static String _dirFor(String namespace) => namespace.isEmpty ? _baseDir : '$_baseDir/accounts/$namespace'; static String _ensureDir(String path) { Directory(path).createSync(recursive: true); return path; } }