@@ -6,6 +6,7 @@ import 'dart:async';
66import 'dart:convert' ;
77import 'dart:typed_data' ;
88
9+ import '../../build.dart' ;
910import '../asset/id.dart' ;
1011import 'lru_cache.dart' ;
1112
@@ -16,6 +17,9 @@ abstract interface class FilesystemCache {
1617 /// Clears all [ids] from all caches.
1718 void invalidate (Iterable <AssetId > ids);
1819
20+ /// Flushes pending writes and deletes.
21+ void flush ();
22+
1923 /// Whether [id] exists.
2024 ///
2125 /// Returns a cached result if available, or caches and returns `ifAbsent()` .
@@ -26,6 +30,17 @@ abstract interface class FilesystemCache {
2630 /// Returns a cached result if available, or caches and returns `ifAbsent()` .
2731 Uint8List readAsBytes (AssetId id, {required Uint8List Function () ifAbsent});
2832
33+ /// Writes [contents] to [id] .
34+ ///
35+ /// [writer] is a function that does the actual write. If this cache does
36+ /// write caching, it is not called until [flush] , and might not be called at
37+ /// all if another write to the same asset happens first.
38+ void writeAsBytes (
39+ AssetId id,
40+ List <int > contents, {
41+ required void Function () writer,
42+ });
43+
2944 /// Reads [id] as a `String` .
3045 ///
3146 /// Returns a cached result if available, or caches and returns `ifAbsent()` .
@@ -34,6 +49,25 @@ abstract interface class FilesystemCache {
3449 Encoding encoding = utf8,
3550 required Uint8List Function () ifAbsent,
3651 });
52+
53+ /// Writes [contents] to [id] .
54+ ///
55+ /// [writer] is a function that does the actual write. If this cache does
56+ /// write caching, it is not called until [flush] , and might not be called at
57+ /// all if another write to the same asset happens first.
58+ void writeAsString (
59+ AssetId id,
60+ String contents, {
61+ Encoding encoding = utf8,
62+ required void Function () writer,
63+ });
64+
65+ /// Deletes [id] .
66+ ///
67+ /// [deleter] is a function that does the actual delete. If this cache does
68+ /// write caching, it is not called until [flush] , and might not be called at
69+ /// all if another write to the same asset happens first.
70+ void delete (AssetId id, {required void Function () deleter});
3771}
3872
3973/// [FilesystemCache] that always reads from the underlying source.
@@ -43,19 +77,40 @@ class PassthroughFilesystemCache implements FilesystemCache {
4377 @override
4478 Future <void > invalidate (Iterable <AssetId > ids) async {}
4579
80+ @override
81+ void flush () {}
82+
4683 @override
4784 bool exists (AssetId id, {required bool Function () ifAbsent}) => ifAbsent ();
4885
4986 @override
5087 Uint8List readAsBytes (AssetId id, {required Uint8List Function () ifAbsent}) =>
5188 ifAbsent ();
5289
90+ @override
91+ void writeAsBytes (
92+ AssetId id,
93+ List <int > contents, {
94+ required void Function () writer,
95+ }) => writer ();
96+
5397 @override
5498 String readAsString (
5599 AssetId id, {
56100 Encoding encoding = utf8,
57101 required Uint8List Function () ifAbsent,
58102 }) => encoding.decode (ifAbsent ());
103+
104+ @override
105+ void writeAsString (
106+ AssetId id,
107+ String contents, {
108+ Encoding encoding = utf8,
109+ required void Function () writer,
110+ }) => writer ();
111+
112+ @override
113+ void delete (AssetId id, {required void Function () deleter}) => deleter ();
59114}
60115
61116/// [FilesystemCache] that stores data in memory.
@@ -83,8 +138,13 @@ class InMemoryFilesystemCache implements FilesystemCache {
83138 (value) => value.length,
84139 );
85140
141+ final _pendingWrites = < AssetId , _PendingWrite > {};
142+
86143 @override
87144 Future <void > invalidate (Iterable <AssetId > ids) async {
145+ if (_pendingWrites.isNotEmpty) {
146+ throw StateError ("Can't invalidate while there are pending writes." );
147+ }
88148 for (var id in ids) {
89149 _existsCache.remove (id);
90150 _bytesContentCache.remove (id);
@@ -93,11 +153,30 @@ class InMemoryFilesystemCache implements FilesystemCache {
93153 }
94154
95155 @override
96- bool exists (AssetId id, {required bool Function () ifAbsent}) =>
97- _existsCache.putIfAbsent (id, ifAbsent);
156+ void flush () {
157+ for (final write in _pendingWrites.values) {
158+ write.writer ();
159+ }
160+ _pendingWrites.clear ();
161+ }
162+
163+ @override
164+ bool exists (AssetId id, {required bool Function () ifAbsent}) {
165+ final maybePendingWrite = _pendingWrites[id];
166+ if (maybePendingWrite != null ) {
167+ return ! maybePendingWrite.isDelete;
168+ }
169+ return _existsCache.putIfAbsent (id, ifAbsent);
170+ }
98171
99172 @override
100173 Uint8List readAsBytes (AssetId id, {required Uint8List Function () ifAbsent}) {
174+ final maybePendingWrite = _pendingWrites[id];
175+ if (maybePendingWrite != null ) {
176+ // Throws if it's a delete; callers should check [exists] before reading.
177+ return maybePendingWrite.bytes! ;
178+ }
179+
101180 final maybeResult = _bytesContentCache[id];
102181 if (maybeResult != null ) return maybeResult;
103182
@@ -106,27 +185,87 @@ class InMemoryFilesystemCache implements FilesystemCache {
106185 return result;
107186 }
108187
188+ @override
189+ void writeAsBytes (
190+ AssetId id,
191+ List <int > contents, {
192+ required void Function () writer,
193+ }) {
194+ _stringContentCache.remove (id);
195+ _writeBytes (id, contents, writer: writer);
196+ }
197+
198+ void _writeBytes (
199+ AssetId id,
200+ List <int > contents, {
201+ required void Function () writer,
202+ }) {
203+ final uint8ListContents =
204+ contents is Uint8List ? contents : Uint8List .fromList (contents);
205+ _bytesContentCache[id] = uint8ListContents;
206+ _existsCache[id] = true ;
207+ _pendingWrites[id] = _PendingWrite (
208+ writer: writer,
209+ bytes: uint8ListContents,
210+ );
211+ }
212+
109213 @override
110214 String readAsString (
111215 AssetId id, {
112216 Encoding encoding = utf8,
113217 required Uint8List Function () ifAbsent,
114218 }) {
219+ // Encodings other than utf8 do not use `_stringContentCache`. Read as
220+ // bytes then convert, instead.
115221 if (encoding != utf8) {
116222 final bytes = readAsBytes (id, ifAbsent: ifAbsent);
117223 return encoding.decode (bytes);
118224 }
119225
226+ // Check `_stringContentCache` first to use it as a cache for conversion of
227+ // bytes from _pendingWrites.
120228 final maybeResult = _stringContentCache[id];
121229 if (maybeResult != null ) return maybeResult;
122230
123- var bytes = _bytesContentCache[id];
124- if (bytes == null ) {
125- bytes = ifAbsent ();
126- _bytesContentCache[id] = bytes;
127- }
231+ final bytes = readAsBytes (id, ifAbsent: ifAbsent);
128232 final result = utf8.decode (bytes);
129233 _stringContentCache[id] = result;
130234 return result;
131235 }
236+
237+ @override
238+ void writeAsString (
239+ AssetId id,
240+ String contents, {
241+ Encoding encoding = utf8,
242+ required void Function () writer,
243+ }) {
244+ // Encodings other than utf8 do not use `_stringContentCache`.
245+ if (encoding == utf8) {
246+ _stringContentCache[id] = contents;
247+ } else {
248+ _stringContentCache.remove (id);
249+ }
250+ final bytes = encoding.encode (contents);
251+ _writeBytes (id, bytes, writer: writer);
252+ }
253+
254+ @override
255+ void delete (AssetId id, {required void Function () deleter}) {
256+ _stringContentCache.remove (id);
257+ _bytesContentCache.remove (id);
258+ _existsCache[id] = false ;
259+ _pendingWrites[id] = _PendingWrite (writer: deleter);
260+ }
261+ }
262+
263+ /// The data that will be written on flush; used for reads before flush.
264+ class _PendingWrite {
265+ final void Function () writer;
266+ final Uint8List ? bytes;
267+
268+ _PendingWrite ({required this .writer, this .bytes});
269+
270+ bool get isDelete => bytes == null ;
132271}
0 commit comments