mirror of
https://github.com/kuhyx/diet-guard.git
synced 2026-11-11 12:27:42 +01:00
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
file-length / file length <= 250 lines (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
pre-commit / pre-commit (push) Waiting to run
Tests / test (push) Waiting to run
release-apk / release (APK) (push) Waiting to run
Split out from the free-day change only by sequence -- diet-guard's pre-push CI mirror runs the freshness gate over --all-files, so these were already blocking a push before that work started. permission_handler 13.0.1 -> 13.0.2 and workmanager 0.10.9 -> 0.10.10 are patch bumps. very_good_analysis ^10.3.0 -> ^11.0.0 is the major one, and it newly flags 8 async_return_with_no_await sites across the desktop wrapper and the sync/Firebase services. Each is now `return await`, not a suppression. The rule is right in every one of these cases: without the await the future escapes the enclosing async function, so a rejection surfaces at the caller with a stack trace that no longer points at the code that made the call. background_check_service.dart:33 is the clearest example -- its own comment says the push "is retried with backoff rather than silently dropped", which is only true if the failure happens inside the function. Nothing in CI or pre-commit runs `flutter analyze`, so this would not have failed anything. Leaving a newly-red analyzer for the next person to find is how a repo stops being analyzable at all. flutter analyze clean, 706 Flutter tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
88 lines
3.1 KiB
Dart
88 lines
3.1 KiB
Dart
/// IndexedDB-backed [DocumentStore], independent of how the database was
|
|
/// opened.
|
|
///
|
|
/// Split from `document_store_web.dart` so a plain VM test can drive it
|
|
/// against `idb_shim`'s in-memory backend: the browser factory
|
|
/// (`idb_browser.dart`) cannot be imported outside a web compile, and this is
|
|
/// the code a wiped Chrome profile depends on for recovery.
|
|
library;
|
|
|
|
import 'package:diet_guard_app/services/desktop_wrapper.dart';
|
|
import 'package:diet_guard_app/services/document_store.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:idb_shim/idb_shim.dart';
|
|
|
|
/// [DocumentStore] backed by IndexedDB, mirrored to the desktop wrapper's
|
|
/// on-disk copy.
|
|
///
|
|
/// IndexedDB rather than `localStorage`: the latter caps at roughly 5-10MB per
|
|
/// origin and is evicted more eagerly, and these documents are the desktop
|
|
/// device's primary copy of the food log.
|
|
///
|
|
/// The wrapper mirror is the recovery path for a wiped Chrome profile. GitHub
|
|
/// sync would eventually restore the log too, but only what other devices have
|
|
/// already seen -- a meal logged on this desktop and never pushed exists
|
|
/// nowhere else. Reads fall back to the mirror when IndexedDB is empty; writes
|
|
/// to it are best-effort so the app still works opened in a plain browser tab
|
|
/// with no wrapper running.
|
|
class IndexedDbDocumentStore implements DocumentStore {
|
|
/// Creates a store over an already-open [database], mirroring to [baseUrl].
|
|
IndexedDbDocumentStore(
|
|
this._database, {
|
|
this.baseUrl = desktopWrapperOrigin,
|
|
http.Client? httpClient,
|
|
}) : _client = httpClient ?? http.Client();
|
|
|
|
final Database _database;
|
|
final http.Client _client;
|
|
|
|
/// Origin of the desktop wrapper.
|
|
final String baseUrl;
|
|
|
|
/// Object store holding one entry per document name.
|
|
static const storeName = 'documents';
|
|
|
|
/// IndexedDB database name.
|
|
static const databaseName = 'diet_guard';
|
|
|
|
@override
|
|
Future<String?> read(String name) async {
|
|
final txn = _database.transaction(storeName, idbModeReadOnly);
|
|
final value = await txn.objectStore(storeName).getObject(name);
|
|
await txn.completed;
|
|
if (value is String) return value;
|
|
// Empty IndexedDB: either a first run or a cleared profile. Recover from
|
|
// the wrapper's disk copy rather than silently starting from nothing.
|
|
return await _readMirror(name);
|
|
}
|
|
|
|
@override
|
|
Future<void> write(String name, String contents) async {
|
|
final txn = _database.transaction(storeName, idbModeReadWrite);
|
|
await txn.objectStore(storeName).put(contents, name);
|
|
await txn.completed;
|
|
await _writeMirror(name, contents);
|
|
}
|
|
|
|
Future<String?> _readMirror(String name) async {
|
|
try {
|
|
final response = await _client.get(_uriFor(name));
|
|
if (response.statusCode != 200 || response.body.isEmpty) return null;
|
|
return response.body;
|
|
} on Exception {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
Future<void> _writeMirror(String name, String contents) async {
|
|
try {
|
|
await _client.post(_uriFor(name), body: contents);
|
|
} on Exception {
|
|
// Best-effort by design; see the class docs.
|
|
}
|
|
}
|
|
|
|
Uri _uriFor(String name) =>
|
|
Uri.parse('$baseUrl${WrapperPaths.documents}$name');
|
|
}
|