Files
Client/lib/storage/account_storage.dart
T

88 lines
3.2 KiB
Dart

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<String, Storage> _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<void> 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<void> 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<void> 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<Storage> _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;
}
}