feat(storage): one-time SQLite to sidecar migration
Some checks failed
CI / Windows build (push) Has been cancelled

Phase 5. On first launch with a valid vault, migrate legacy SQLite
data into vault sidecars so nothing is lost on upgrade.

- SqliteToSidecarMigrator: documents (+ per-page ink, scratch-links
  + scratchpads, bookmarks) -> notebook folder + <file>.badnote.json;
  notes (+ strokes) -> notebook.badnote.json. Reuses existing JSON.
- Idempotent (skips already-migrated targets); missing source files
  still get their annotations migrated.
- DB relocates to <vault>/.badnote/index.sqlite; the legacy DB is
  renamed to .premigration ONLY after a successful pass, so a failed
  migration leaves data intact and the run-once flag unset.
- main.dart runs it once, gated on vaultMigrationDone.

Golden migrator tests (seeded legacy DB -> sidecars, idempotent
re-run, legacy preserved). analyze clean, tests green.
This commit is contained in:
2026-06-24 23:05:10 +08:00
parent f4f0853eae
commit 4886f1b2df
6 changed files with 1070 additions and 4 deletions

View File

@@ -92,6 +92,10 @@ class VaultService {
/// SharedPreferences key under which the vault root path is stored.
static const String vaultRootKey = 'vaultRoot';
/// SharedPreferences key gating the one-time SQLite→sidecar migration
/// (Phase 5). Set true once the migration completes so it never re-runs.
static const String vaultMigrationDoneKey = 'vaultMigrationDone';
final SharedPreferences _prefs;
VaultService._(this._prefs);
@@ -132,6 +136,15 @@ class VaultService {
await _prefs.remove(vaultRootKey);
}
/// True once the one-time SQLite→sidecar migration (Phase 5) has completed.
/// When false, startup runs the migrator before opening the home screen.
bool get vaultMigrationDone => _prefs.getBool(vaultMigrationDoneKey) ?? false;
/// Mark the one-time SQLite→sidecar migration as done so it never re-runs.
Future<void> setVaultMigrationDone() async {
await _prefs.setBool(vaultMigrationDoneKey, true);
}
/// 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