@@ -6,6 +6,7 @@ import 'dart:async';
6
6
import 'dart:convert' ;
7
7
import 'dart:typed_data' ;
8
8
9
+ import '../../build.dart' ;
9
10
import '../asset/id.dart' ;
10
11
import 'lru_cache.dart' ;
11
12
@@ -16,6 +17,9 @@ abstract interface class FilesystemCache {
16
17
/// Clears all [ids] from all caches.
17
18
void invalidate (Iterable <AssetId > ids);
18
19
20
+ /// Flushes pending writes and deletes.
21
+ void flush ();
22
+
19
23
/// Whether [id] exists.
20
24
///
21
25
/// Returns a cached result if available, or caches and returns `ifAbsent()` .
@@ -26,6 +30,17 @@ abstract interface class FilesystemCache {
26
30
/// Returns a cached result if available, or caches and returns `ifAbsent()` .
27
31
Uint8List readAsBytes (AssetId id, {required Uint8List Function () ifAbsent});
28
32
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
+
29
44
/// Reads [id] as a `String` .
30
45
///
31
46
/// Returns a cached result if available, or caches and returns `ifAbsent()` .
@@ -34,6 +49,25 @@ abstract interface class FilesystemCache {
34
49
Encoding encoding = utf8,
35
50
required Uint8List Function () ifAbsent,
36
51
});
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});
37
71
}
38
72
39
73
/// [FilesystemCache] that always reads from the underlying source.
@@ -43,19 +77,40 @@ class PassthroughFilesystemCache implements FilesystemCache {
43
77
@override
44
78
Future <void > invalidate (Iterable <AssetId > ids) async {}
45
79
80
+ @override
81
+ void flush () {}
82
+
46
83
@override
47
84
bool exists (AssetId id, {required bool Function () ifAbsent}) => ifAbsent ();
48
85
49
86
@override
50
87
Uint8List readAsBytes (AssetId id, {required Uint8List Function () ifAbsent}) =>
51
88
ifAbsent ();
52
89
90
+ @override
91
+ void writeAsBytes (
92
+ AssetId id,
93
+ List <int > contents, {
94
+ required void Function () writer,
95
+ }) => writer ();
96
+
53
97
@override
54
98
String readAsString (
55
99
AssetId id, {
56
100
Encoding encoding = utf8,
57
101
required Uint8List Function () ifAbsent,
58
102
}) => 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 ();
59
114
}
60
115
61
116
/// [FilesystemCache] that stores data in memory.
@@ -83,8 +138,13 @@ class InMemoryFilesystemCache implements FilesystemCache {
83
138
(value) => value.length,
84
139
);
85
140
141
+ final _pendingWrites = < AssetId , _PendingWrite > {};
142
+
86
143
@override
87
144
Future <void > invalidate (Iterable <AssetId > ids) async {
145
+ if (_pendingWrites.isNotEmpty) {
146
+ throw StateError ("Can't invalidate while there are pending writes." );
147
+ }
88
148
for (var id in ids) {
89
149
_existsCache.remove (id);
90
150
_bytesContentCache.remove (id);
@@ -93,11 +153,30 @@ class InMemoryFilesystemCache implements FilesystemCache {
93
153
}
94
154
95
155
@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
+ }
98
171
99
172
@override
100
173
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
+
101
180
final maybeResult = _bytesContentCache[id];
102
181
if (maybeResult != null ) return maybeResult;
103
182
@@ -106,27 +185,87 @@ class InMemoryFilesystemCache implements FilesystemCache {
106
185
return result;
107
186
}
108
187
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
+
109
213
@override
110
214
String readAsString (
111
215
AssetId id, {
112
216
Encoding encoding = utf8,
113
217
required Uint8List Function () ifAbsent,
114
218
}) {
219
+ // Encodings other than utf8 do not use `_stringContentCache`. Read as
220
+ // bytes then convert, instead.
115
221
if (encoding != utf8) {
116
222
final bytes = readAsBytes (id, ifAbsent: ifAbsent);
117
223
return encoding.decode (bytes);
118
224
}
119
225
226
+ // Check `_stringContentCache` first to use it as a cache for conversion of
227
+ // bytes from _pendingWrites.
120
228
final maybeResult = _stringContentCache[id];
121
229
if (maybeResult != null ) return maybeResult;
122
230
123
- var bytes = _bytesContentCache[id];
124
- if (bytes == null ) {
125
- bytes = ifAbsent ();
126
- _bytesContentCache[id] = bytes;
127
- }
231
+ final bytes = readAsBytes (id, ifAbsent: ifAbsent);
128
232
final result = utf8.decode (bytes);
129
233
_stringContentCache[id] = result;
130
234
return result;
131
235
}
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 ;
132
271
}
0 commit comments