mirror of
https://github.com/kuhyx/todo-app.git
synced 2026-11-11 21:34:03 +01:00
Standardizes on the same stricter lint set + shared analysis_options.yaml used by diet-guard/app and the new habit_stack app. Fixes all 127 issues surfaced: dart fix --apply for imports/cascades/sorting, unawaited() on fire-and-forget Futures, bare catch(e) narrowed to on Exception catch(e) (matching diet_guard's existing pattern), one documented ignore for a deliberate TypeError catch around untrusted peer JSON, and missing public-member doc comments throughout. No behavior change: 174/174 tests pass, 100% line coverage maintained. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
73 lines
2.6 KiB
Dart
73 lines
2.6 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:todo/data/note.dart';
|
|
import 'package:todo/sync/notes_markdown.dart';
|
|
|
|
/// Keeps an always-current Markdown backup of all notes on local disk, and
|
|
/// recovers from it on launch.
|
|
///
|
|
/// This is a third durability layer alongside GitHub auto-sync and Android
|
|
/// Auto Backup: a plain human-readable file the user (or an LLM) can read
|
|
/// directly. File IO is injected ([reader]/[writer]) so the class is pure and
|
|
/// fully testable; the platform-specific path lives in the caller.
|
|
///
|
|
/// Writes are debounced so a burst of keystrokes produces a single export.
|
|
///
|
|
/// The note snapshot is pulled lazily via [fetch] only when the debounce
|
|
/// fires — never per keystroke — so serialising every note stays off the
|
|
/// typing hot path and the cost is independent of how fast the user types.
|
|
class LocalBackup {
|
|
/// Creates a [LocalBackup] wiring the note [fetch]/[reader]/[writer].
|
|
LocalBackup({
|
|
required this.fetch,
|
|
required this.reader,
|
|
required this.writer,
|
|
this.debounce = const Duration(seconds: 2),
|
|
});
|
|
|
|
/// Pulls the current notes to export. Invoked once per debounced write, so
|
|
/// the O(notes) query + serialization happens at most once per idle window.
|
|
final Future<List<Note>> Function() fetch;
|
|
|
|
/// Reads the backup file's contents, or null if it does not exist.
|
|
final Future<String?> Function() reader;
|
|
|
|
/// Writes the given Markdown to the backup file (overwriting it).
|
|
final Future<void> Function(String markdown) writer;
|
|
|
|
/// How long to wait after the last change before writing.
|
|
final Duration debounce;
|
|
|
|
Timer? _timer;
|
|
|
|
/// Schedules a debounced backup write. Repeated calls reset the timer, so a
|
|
/// burst of writes collapses to one export of the latest snapshot. A zero
|
|
/// [debounce] exports immediately (and schedules no timer).
|
|
void schedule() {
|
|
_timer?.cancel();
|
|
if (debounce == Duration.zero) {
|
|
unawaited(_export());
|
|
} else {
|
|
_timer = Timer(debounce, () => unawaited(_export()));
|
|
}
|
|
}
|
|
|
|
/// Pulls the latest notes and writes the Markdown backup. Returned so tests
|
|
/// (and a zero-debounce caller) can await the write.
|
|
Future<void> _export() async {
|
|
final notes = await fetch();
|
|
await writer(NotesMarkdown.export(notes));
|
|
}
|
|
|
|
/// Reads the backup file and parses it into notes for recovery. Returns an
|
|
/// empty list when there is no (usable) backup.
|
|
Future<List<Note>> recover() async {
|
|
final contents = await reader();
|
|
if (contents == null || contents.trim().isEmpty) return const [];
|
|
return NotesMarkdown.parse(contents);
|
|
}
|
|
|
|
/// Cancels any pending write. Call from the owner's dispose.
|
|
void dispose() => _timer?.cancel();
|
|
}
|