Skip to content

Commit a6cd204

Browse files
authored
Implement MemoryFile. (flutter#13)
This completes the implementation of the in-memory FileSystem backend. Next change will add tests.
1 parent 0cb5e78 commit a6cd204

File tree

2 files changed

+173
-33
lines changed

2 files changed

+173
-33
lines changed

lib/src/backends/memory/memory_file.dart

Lines changed: 165 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,48 @@ class _MemoryFile extends _MemoryFileSystemEntity implements File {
4444
Future<File> copy(String newPath) async => copySync(newPath);
4545

4646
@override
47-
File copySync(String newPath) => throw new UnimplementedError('TODO');
47+
File copySync(String newPath) {
48+
_FileNode sourceNode = backing;
49+
fileSystem._findNode(
50+
newPath,
51+
segmentVisitor: (
52+
_DirectoryNode parent,
53+
String childName,
54+
_Node child,
55+
int currentSegment,
56+
int finalSegment,
57+
) {
58+
if (currentSegment == finalSegment) {
59+
if (child != null) {
60+
if (_isLink(child)) {
61+
StringBuffer ledger = new StringBuffer();
62+
child = _resolveLinks(child, () => newPath, ledger: ledger);
63+
_checkExists(child, () => newPath);
64+
parent = child.parent;
65+
childName = fileSystem._context.basename(ledger.toString());
66+
assert(parent.children.containsKey(childName));
67+
}
68+
if (child.type != expectedType) {
69+
throw new io.FileSystemException(
70+
'Is a ${child.type.toString().toLowerCase()}', newPath);
71+
}
72+
parent.children.remove(childName);
73+
}
74+
_FileNode newNode = new _FileNode(parent);
75+
newNode.copyFrom(sourceNode);
76+
parent.children[childName] = newNode;
77+
}
78+
return child;
79+
},
80+
);
81+
return _clone(newPath);
82+
}
4883

4984
@override
5085
Future<int> length() async => lengthSync();
5186

5287
@override
53-
int lengthSync() => throw new UnimplementedError('TODO');
88+
int lengthSync() => (backing as _FileNode).size;
5489

5590
@override
5691
File get absolute => super.absolute;
@@ -59,7 +94,7 @@ class _MemoryFile extends _MemoryFileSystemEntity implements File {
5994
Future<DateTime> lastModified() async => lastModifiedSync();
6095

6196
@override
62-
DateTime lastModifiedSync() => throw new UnimplementedError('TODO');
97+
DateTime lastModifiedSync() => (backing as _FileNode).stat.modified;
6398

6499
@override
65100
Future<io.RandomAccessFile> open(
@@ -71,53 +106,53 @@ class _MemoryFile extends _MemoryFileSystemEntity implements File {
71106
throw new UnimplementedError('TODO');
72107

73108
@override
74-
Stream<List<int>> openRead([int start, int end]) =>
75-
throw new UnimplementedError('TODO');
109+
Stream<List<int>> openRead([int start, int end]) {
110+
_FileNode node = backing;
111+
return new Stream.fromIterable(<List<int>>[node.content]);
112+
}
76113

77114
@override
78115
io.IOSink openWrite({
79116
io.FileMode mode: io.FileMode.WRITE,
80117
Encoding encoding: UTF8,
81-
}) =>
82-
throw new UnimplementedError('TODO');
118+
}) {
119+
_checkWriteMode(mode);
120+
createSync();
121+
_FileNode node = backing;
122+
_truncateFileIfNecessary(node, mode);
123+
return new _FileSink(node, encoding);
124+
}
83125

84126
@override
85-
Future<List<int>> readAsBytes() {
86-
throw new UnimplementedError('TODO');
87-
}
127+
Future<List<int>> readAsBytes() async => readAsBytesSync();
88128

89129
@override
90-
List<int> readAsBytesSync() {
91-
throw new UnimplementedError('TODO');
92-
}
130+
List<int> readAsBytesSync() => (backing as _FileNode).content;
93131

94132
@override
95-
Future<String> readAsString({Encoding encoding: UTF8}) {
96-
throw new UnimplementedError('TODO');
97-
}
133+
Future<String> readAsString({Encoding encoding: UTF8}) async =>
134+
readAsStringSync(encoding: encoding);
98135

99136
@override
100-
String readAsStringSync({Encoding encoding: UTF8}) {
101-
throw new UnimplementedError('TODO');
102-
}
137+
String readAsStringSync({Encoding encoding: UTF8}) =>
138+
encoding.decode(readAsBytesSync());
103139

104140
@override
105-
Future<List<String>> readAsLines({Encoding encoding: UTF8}) {
106-
throw new UnimplementedError('TODO');
107-
}
141+
Future<List<String>> readAsLines({Encoding encoding: UTF8}) async =>
142+
readAsLinesSync(encoding: encoding);
108143

109144
@override
110-
List<String> readAsLinesSync({Encoding encoding: UTF8}) {
111-
throw new UnimplementedError('TODO');
112-
}
145+
List<String> readAsLinesSync({Encoding encoding: UTF8}) =>
146+
readAsStringSync(encoding: encoding).split('\n');
113147

114148
@override
115149
Future<File> writeAsBytes(
116150
List<int> bytes, {
117151
io.FileMode mode: io.FileMode.WRITE,
118152
bool flush: false,
119-
}) {
120-
throw new UnimplementedError('TODO');
153+
}) async {
154+
writeAsBytesSync(bytes, mode: mode, flush: flush);
155+
return this;
121156
}
122157

123158
@override
@@ -126,7 +161,11 @@ class _MemoryFile extends _MemoryFileSystemEntity implements File {
126161
io.FileMode mode: io.FileMode.WRITE,
127162
bool flush: false,
128163
}) {
129-
throw new UnimplementedError('TODO');
164+
_checkWriteMode(mode);
165+
createSync();
166+
_FileNode node = backing;
167+
_truncateFileIfNecessary(node, mode);
168+
node.content.addAll(bytes);
130169
}
131170

132171
@override
@@ -135,8 +174,9 @@ class _MemoryFile extends _MemoryFileSystemEntity implements File {
135174
io.FileMode mode: io.FileMode.WRITE,
136175
Encoding encoding: UTF8,
137176
bool flush: false,
138-
}) {
139-
throw new UnimplementedError('TODO');
177+
}) async {
178+
writeAsStringSync(contents, mode: mode, encoding: encoding, flush: flush);
179+
return this;
140180
}
141181

142182
@override
@@ -145,10 +185,102 @@ class _MemoryFile extends _MemoryFileSystemEntity implements File {
145185
io.FileMode mode: io.FileMode.WRITE,
146186
Encoding encoding: UTF8,
147187
bool flush: false,
148-
}) {
149-
throw new UnimplementedError('TODO');
150-
}
188+
}) =>
189+
writeAsBytesSync(encoding.encode(contents), mode: mode, flush: flush);
151190

152191
@override
153192
File _clone(String path) => new _MemoryFile(fileSystem, path);
193+
194+
void _checkWriteMode(io.FileMode mode) {
195+
if (mode != io.FileMode.WRITE &&
196+
mode != io.FileMode.APPEND &&
197+
mode != io.FileMode.WRITE_ONLY &&
198+
mode != io.FileMode.WRITE_ONLY_APPEND) {
199+
throw new ArgumentError.value(mode, 'mode',
200+
'Must be either WRITE, APPEND, WRITE_ONLY, or WRITE_ONLY_APPEND');
201+
}
202+
}
203+
204+
void _truncateFileIfNecessary(_FileNode node, io.FileMode mode) {
205+
if (mode == io.FileMode.WRITE || mode == io.FileMode.WRITE_ONLY) {
206+
node.content.clear();
207+
}
208+
}
209+
}
210+
211+
/// Implementation of an [io.IOSink] that's backed by a [_FileNode].
212+
class _FileSink implements io.IOSink {
213+
final _FileNode _node;
214+
final Completer<Null> _doneCompleter = new Completer<Null>();
215+
Encoding _encoding;
216+
bool _isClosed = false;
217+
218+
_FileSink(this._node, this._encoding) {
219+
_checkNotNull(_encoding);
220+
}
221+
222+
dynamic _checkNotNull(dynamic value) {
223+
if (value == null) {
224+
throw new ArgumentError.notNull();
225+
}
226+
return value;
227+
}
228+
229+
@override
230+
Encoding get encoding => _encoding;
231+
232+
@override
233+
set encoding(Encoding encoding) => _encoding = _checkNotNull(encoding);
234+
235+
@override
236+
void add(List<int> data) {
237+
if (!_isClosed) {
238+
_node.content.addAll(data);
239+
}
240+
}
241+
242+
@override
243+
void write(Object obj) => add(encoding.encode(obj?.toString() ?? 'null'));
244+
245+
@override
246+
void writeAll(Iterable objects, [String separator = ""]) {
247+
bool firstIter = true;
248+
for (dynamic obj in objects) {
249+
if (!firstIter && separator != null) {
250+
write(separator);
251+
}
252+
firstIter = false;
253+
write(obj);
254+
}
255+
}
256+
257+
@override
258+
void writeln([Object obj = ""]) {
259+
write(obj);
260+
write('\n');
261+
}
262+
263+
@override
264+
void writeCharCode(int charCode) => write(new String.fromCharCode(charCode));
265+
266+
@override
267+
void addError(error, [StackTrace stackTrace]) =>
268+
throw new UnsupportedError('addError');
269+
270+
@override
271+
Future addStream(Stream<List<int>> stream) =>
272+
stream.forEach((List<int> data) => add(data));
273+
274+
@override
275+
Future flush() => new Future.value();
276+
277+
@override
278+
Future close() {
279+
_isClosed = true;
280+
_doneCompleter.complete();
281+
return _doneCompleter.future;
282+
}
283+
284+
@override
285+
Future get done => _doneCompleter.future;
154286
}

lib/src/backends/memory/node.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,14 @@ class _FileNode extends _RealNode {
132132

133133
@override
134134
int get size => content.length;
135+
136+
void copyFrom(_FileNode source) {
137+
changed = new DateTime.now().millisecondsSinceEpoch;
138+
modified = new DateTime.now().millisecondsSinceEpoch;
139+
accessed = source.accessed;
140+
mode = source.mode;
141+
content = new List<int>.from(content);
142+
}
135143
}
136144

137145
/// Class that represents the backing for an in-memory symbolic link.

0 commit comments

Comments
 (0)