import 'dart:io'; import 'dart:typed_data'; import 'package:path_provider/path_provider.dart'; /// Opaque binary records too large for the keychain, such as encrypted /// editor drafts. Callers encrypt before writing; nothing here is secret. abstract interface class BlobStore { Future read(String name); Future write(String name, Uint8List bytes); Future delete(String name); } class MemoryBlobStore implements BlobStore { final values = {}; @override Future read(String name) async => values[name]; @override Future write(String name, Uint8List bytes) async => values[name] = Uint8List.fromList(bytes); @override Future delete(String name) async => values.remove(name); } /// Files in the app's private support directory, written atomically /// (temporary file, flush, rename) so a crash never leaves half a record. class FileBlobStore implements BlobStore { Future? _root; Future get root => _root ??= () async { final base = await getApplicationSupportDirectory(); return Directory('${base.path}/sm-blobs').create(recursive: true); }(); static String _check(String name) { if (!RegExp(r'^[A-Za-z0-9_-]{1,80}$').hasMatch(name)) { throw ArgumentError.value(name, 'name'); } return name; } @override Future read(String name) async { final file = File('${(await root).path}/${_check(name)}'); return await file.exists() ? file.readAsBytes() : null; } @override Future write(String name, Uint8List bytes) async { final dir = await root; final target = File('${dir.path}/${_check(name)}'); final temp = File('${dir.path}/.${_check(name)}.tmp'); await temp.writeAsBytes(bytes, flush: true); await temp.rename(target.path); } @override Future delete(String name) async { final file = File('${(await root).path}/${_check(name)}'); if (await file.exists()) await file.delete(); } }