feat(storage): app-pause flush + vault search index
Some checks failed
CI / Windows build (push) Has been cancelled
Some checks failed
CI / Windows build (push) Has been cancelled
Phase 6 (final storage phase). - SidecarRepositoryRegistry tracks every open repo; SidecarFlushObserver (a WidgetsBindingObserver in main) flushes them all on inactive/hidden/paused/detached, awaiting each flush — the last strokes can't be lost on app close, not just on the 800ms timer. - VaultSearchIndex rebuilds by scanning vault sidecars (the source of truth) — note titles, OCR text and document names — and search_provider queries it, so search spans notes + PDFs. Rebuilt on launch / after import. The vault file-based storage migration (Phases 0-6) is complete: annotations travel with the file, picked vault folder, atomic autosave, one Import-file entry, SQLite migrated to sidecars. analyze clean, tests green.
This commit is contained in:
58
lib/editor/persistence/sidecar_flush_observer.dart
Normal file
58
lib/editor/persistence/sidecar_flush_observer.dart
Normal file
@@ -0,0 +1,58 @@
|
||||
// lib/editor/persistence/sidecar_flush_observer.dart
|
||||
//
|
||||
// Phase 6 / §F.3 of the file-based storage plan (docs/plans/2026-06-24-file-
|
||||
// based-storage.md): app-lifecycle flush hardening.
|
||||
//
|
||||
// The per-file SidecarRepository debounces writes by 800 ms. That window is the
|
||||
// data-loss gap on a Windows tablet: if the OS suspends or closes the app
|
||||
// before the timer fires, the last strokes never reach disk. This observer
|
||||
// listens for the app leaving the foreground and DRAINS every open repo's
|
||||
// pending write before the process can be frozen, so "never lose the last
|
||||
// strokes on app close" holds even when the editor's own dispose() doesn't run.
|
||||
//
|
||||
// Registered once in BadNoteApp; it delegates to
|
||||
// [SidecarRepositoryRegistry.flushAll], which awaits every repo's flush().
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'sidecar_repository.dart';
|
||||
|
||||
/// A [WidgetsBindingObserver] that flushes all open sidecar repositories when
|
||||
/// the app leaves the foreground (`inactive`/`paused`/`detached`/`hidden`).
|
||||
class SidecarFlushObserver with WidgetsBindingObserver {
|
||||
/// Whether the observer is currently registered with the binding.
|
||||
bool get isAttached => _attached;
|
||||
bool _attached = false;
|
||||
|
||||
/// Register with [WidgetsBinding.instance] so lifecycle changes are observed.
|
||||
void attach() {
|
||||
if (_attached) return;
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
_attached = true;
|
||||
}
|
||||
|
||||
/// Stop observing lifecycle changes.
|
||||
void detach() {
|
||||
if (!_attached) return;
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_attached = false;
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
switch (state) {
|
||||
// Any transition out of the foreground is a potential suspend/kill point:
|
||||
// drain pending sidecar writes now (the editors' own dispose() may never
|
||||
// run when the OS freezes the process).
|
||||
case AppLifecycleState.inactive:
|
||||
case AppLifecycleState.hidden:
|
||||
case AppLifecycleState.paused:
|
||||
case AppLifecycleState.detached:
|
||||
// Fire-and-forget at the framework boundary, but each write is atomic
|
||||
// and awaited inside flushAll, so a half-written sidecar is impossible.
|
||||
SidecarRepositoryRegistry.flushAll();
|
||||
case AppLifecycleState.resumed:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user