@@ -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 write. 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,6 +185,24 @@ class InMemoryFilesystemCache implements FilesystemCache {
106
185
return result;
107
186
}
108
187
188
+ @override
189
+ @override
190
+ void writeAsBytes (
191
+ AssetId id,
192
+ List <int > contents, {
193
+ required void Function () writer,
194
+ }) {
195
+ _stringContentCache.remove (id);
196
+ final uint8ListContents =
197
+ contents is Uint8List ? contents : Uint8List .fromList (contents);
198
+ _bytesContentCache[id] = uint8ListContents;
199
+ _existsCache[id] = true ;
200
+ _pendingWrites[id] = _PendingWrite (
201
+ writer: writer,
202
+ bytes: uint8ListContents,
203
+ );
204
+ }
205
+
109
206
@override
110
207
String readAsString (
111
208
AssetId id, {
@@ -117,9 +214,19 @@ class InMemoryFilesystemCache implements FilesystemCache {
117
214
return encoding.decode (bytes);
118
215
}
119
216
217
+ // Check _stringContentCache first to use it as a cache for conversion of
218
+ // bytes from _pendingWrites.
120
219
final maybeResult = _stringContentCache[id];
121
220
if (maybeResult != null ) return maybeResult;
122
221
222
+ final maybePendingWrite = _pendingWrites[id];
223
+ if (maybePendingWrite != null ) {
224
+ // Throws if it's a delete; callers should check [exists] before reading.
225
+ final converted = utf8.decode (maybePendingWrite.bytes! );
226
+ _stringContentCache[id] = converted;
227
+ return converted;
228
+ }
229
+
123
230
var bytes = _bytesContentCache[id];
124
231
if (bytes == null ) {
125
232
bytes = ifAbsent ();
@@ -129,4 +236,40 @@ class InMemoryFilesystemCache implements FilesystemCache {
129
236
_stringContentCache[id] = result;
130
237
return result;
131
238
}
239
+
240
+ @override
241
+ void writeAsString (
242
+ AssetId id,
243
+ String contents, {
244
+ Encoding encoding = utf8,
245
+ required void Function () writer,
246
+ }) {
247
+ _stringContentCache[id] = contents;
248
+ _bytesContentCache.remove (id);
249
+ _existsCache[id] = true ;
250
+
251
+ final encoded = encoding.encode (contents);
252
+ _pendingWrites[id] = _PendingWrite (
253
+ writer: writer,
254
+ bytes: encoded is Uint8List ? encoded : Uint8List .fromList (encoded),
255
+ );
256
+ }
257
+
258
+ @override
259
+ void delete (AssetId id, {required void Function () deleter}) {
260
+ _stringContentCache.remove (id);
261
+ _bytesContentCache.remove (id);
262
+ _existsCache[id] = false ;
263
+ _pendingWrites[id] = _PendingWrite (writer: deleter);
264
+ }
265
+ }
266
+
267
+ /// The data that will be written on flush; used for reads before flush.
268
+ class _PendingWrite {
269
+ final void Function () writer;
270
+ final Uint8List ? bytes;
271
+
272
+ _PendingWrite ({required this .writer, this .bytes});
273
+
274
+ bool get isDelete => bytes == null ;
132
275
}
0 commit comments