71 lines
2.5 KiB
Dart
71 lines
2.5 KiB
Dart
|
|
import 'dart:io';
|
||
|
|
|
||
|
|
import 'package:flutter/foundation.dart' show visibleForTesting;
|
||
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
||
|
|
|
||
|
|
/// Records the user-picked vault root folder (an Obsidian-style vault) and
|
||
|
|
/// gates app startup behind a valid choice.
|
||
|
|
///
|
||
|
|
/// The vault root is the single folder under which all notebooks will live.
|
||
|
|
/// Phase 0 only persists the path and validates it exists; no data is moved
|
||
|
|
/// into the vault yet (later phases do that).
|
||
|
|
///
|
||
|
|
/// Persistence is SharedPreferences-backed under [vaultRootKey]. The service is
|
||
|
|
/// easily mockable: inject a [SharedPreferences] (e.g. from
|
||
|
|
/// `SharedPreferences.setMockInitialValues`) via the constructor for tests.
|
||
|
|
class VaultService {
|
||
|
|
/// SharedPreferences key under which the vault root path is stored.
|
||
|
|
static const String vaultRootKey = 'vaultRoot';
|
||
|
|
|
||
|
|
final SharedPreferences _prefs;
|
||
|
|
|
||
|
|
VaultService._(this._prefs);
|
||
|
|
|
||
|
|
static VaultService? _instance;
|
||
|
|
|
||
|
|
/// Singleton accessor, mirroring [DatabaseService.getInstance]. Lazily reads
|
||
|
|
/// the shared [SharedPreferences] instance.
|
||
|
|
static Future<VaultService> getInstance() async {
|
||
|
|
if (_instance != null) return _instance!;
|
||
|
|
final prefs = await SharedPreferences.getInstance();
|
||
|
|
final service = VaultService._(prefs);
|
||
|
|
_instance = service;
|
||
|
|
return service;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Test-only constructor: inject a (typically mock) [SharedPreferences] so
|
||
|
|
/// the vault root can be exercised without platform channels.
|
||
|
|
@visibleForTesting
|
||
|
|
VaultService.forTest(SharedPreferences prefs) : _prefs = prefs;
|
||
|
|
|
||
|
|
/// Test-only: drop the cached singleton so the next [getInstance] rebuilds.
|
||
|
|
@visibleForTesting
|
||
|
|
static void resetForTest() {
|
||
|
|
_instance = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// The currently stored vault root path, or null if none has been chosen.
|
||
|
|
String? get vaultRoot => _prefs.getString(vaultRootKey);
|
||
|
|
|
||
|
|
/// Persist [path] as the vault root.
|
||
|
|
Future<void> setVaultRoot(String path) async {
|
||
|
|
await _prefs.setString(vaultRootKey, path);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Forget the stored vault root (e.g. to re-prompt the user).
|
||
|
|
Future<void> clearVaultRoot() async {
|
||
|
|
await _prefs.remove(vaultRootKey);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// True iff a vault root is set AND that directory currently exists.
|
||
|
|
///
|
||
|
|
/// Returns false when no path is stored or when the stored path no longer
|
||
|
|
/// resolves to a directory (external drive unplugged, folder deleted) — the
|
||
|
|
/// caller then re-prompts rather than silently scattering data elsewhere.
|
||
|
|
Future<bool> vaultRootValid() async {
|
||
|
|
final path = vaultRoot;
|
||
|
|
if (path == null || path.isEmpty) return false;
|
||
|
|
return Directory(path).exists();
|
||
|
|
}
|
||
|
|
}
|