blob: a26c4d0cb8da2fab1309ab96e96e67880da25d0b [file] [log] [blame]
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of file.src.backends.memory;
class _MemoryFile extends _MemoryFileSystemEntity implements File {
_MemoryFile(MemoryFileSystem fileSystem, String path)
: super(fileSystem, path);
_FileNode get _resolvedBackingOrCreate {
_Node node = _backingOrNull;
if (node == null) {
node = _doCreate();
} else {
node = _isLink(node) ? _resolveLinks(node, () => path) : node;
_checkType(expectedType, node.type, () => path);
}
return node;
}
@override
io.FileSystemEntityType get expectedType => io.FileSystemEntityType.FILE;
@override
bool existsSync() => _backingOrNull?.stat?.type == expectedType;
@override
Future<File> create({bool recursive: false}) async {
createSync(recursive: recursive);
return this;
}
@override
void createSync({bool recursive: false}) {
_doCreate(recursive: recursive);
}
_Node _doCreate({bool recursive: false}) {
_Node node = _createSync(
followTailLink: true,
createChild: (_DirectoryNode parent, bool isFinalSegment) {
if (isFinalSegment) {
return new _FileNode(parent);
} else if (recursive) {
return new _DirectoryNode(parent);
}
return null;
},
);
if (node.type != expectedType) {
// There was an existing non-file entity at this object's path
assert(node.type == FileSystemEntityType.DIRECTORY);
throw common.isADirectory(path);
}
return node;
}
@override
Future<File> rename(String newPath) async => renameSync(newPath);
@override
File renameSync(String newPath) => _renameSync(
newPath,
followTailLink: true,
checkType: (_Node node) {
FileSystemEntityType actualType = node.stat.type;
if (actualType != expectedType) {
throw actualType == FileSystemEntityType.NOT_FOUND
? common.noSuchFileOrDirectory(path)
: common.isADirectory(path);
}
},
);
@override
Future<File> copy(String newPath) async => copySync(newPath);
@override
File copySync(String newPath) {
_FileNode sourceNode = _resolvedBacking;
fileSystem._findNode(
newPath,
segmentVisitor: (
_DirectoryNode parent,
String childName,
_Node child,
int currentSegment,
int finalSegment,
) {
if (currentSegment == finalSegment) {
if (child != null) {
if (_isLink(child)) {
List<String> ledger = <String>[];
child = _resolveLinks(child, () => newPath, ledger: ledger);
_checkExists(child, () => newPath);
parent = child.parent;
childName = ledger.last;
assert(parent.children.containsKey(childName));
}
_checkType(expectedType, child.type, () => newPath);
parent.children.remove(childName);
}
_FileNode newNode = new _FileNode(parent);
newNode.copyFrom(sourceNode);
parent.children[childName] = newNode;
}
return child;
},
);
return _clone(newPath);
}
@override
Future<int> length() async => lengthSync();
@override
int lengthSync() => (_resolvedBacking as _FileNode).size;
@override
File get absolute => super.absolute;
@override
Future<DateTime> lastAccessed() async => lastAccessedSync();
@override
DateTime lastAccessedSync() => (_resolvedBacking as _FileNode).stat.accessed;
@override
Future<dynamic> setLastAccessed(DateTime time) async =>
setLastAccessedSync(time);
@override
void setLastAccessedSync(DateTime time) {
_FileNode node = _resolvedBacking;
node.accessed = time.millisecondsSinceEpoch;
}
@override
Future<DateTime> lastModified() async => lastModifiedSync();
@override
DateTime lastModifiedSync() => (_resolvedBacking as _FileNode).stat.modified;
@override
Future<dynamic> setLastModified(DateTime time) async =>
setLastModifiedSync(time);
@override
void setLastModifiedSync(DateTime time) {
_FileNode node = _resolvedBacking;
node.modified = time.millisecondsSinceEpoch;
}
@override
Future<io.RandomAccessFile> open(
{io.FileMode mode: io.FileMode.READ}) async =>
openSync(mode: mode);
@override
io.RandomAccessFile openSync({io.FileMode mode: io.FileMode.READ}) =>
throw new UnimplementedError('TODO');
@override
Stream<List<int>> openRead([int start, int end]) {
try {
_FileNode node = _resolvedBacking;
List<int> content = node.content;
if (start != null) {
content = end == null
? content.sublist(start)
: content.sublist(start, min(end, content.length));
}
return new Stream<List<int>>.fromIterable(<List<int>>[content]);
} catch (e) {
return new Stream<List<int>>.fromFuture(new Future<List<int>>.error(e));
}
}
@override
io.IOSink openWrite({
io.FileMode mode: io.FileMode.WRITE,
Encoding encoding: UTF8,
}) {
if (!_isWriteMode(mode)) {
throw new ArgumentError.value(mode, 'mode',
'Must be either WRITE, APPEND, WRITE_ONLY, or WRITE_ONLY_APPEND');
}
return new _FileSink.fromFile(this, mode, encoding);
}
@override
Future<List<int>> readAsBytes() async => readAsBytesSync();
@override
List<int> readAsBytesSync() => (_resolvedBacking as _FileNode).content;
@override
Future<String> readAsString({Encoding encoding: UTF8}) async =>
readAsStringSync(encoding: encoding);
@override
String readAsStringSync({Encoding encoding: UTF8}) =>
encoding.decode(readAsBytesSync());
@override
Future<List<String>> readAsLines({Encoding encoding: UTF8}) async =>
readAsLinesSync(encoding: encoding);
@override
List<String> readAsLinesSync({Encoding encoding: UTF8}) {
String str = readAsStringSync(encoding: encoding);
return str.isEmpty ? <String>[] : str.split('\n');
}
@override
Future<File> writeAsBytes(
List<int> bytes, {
io.FileMode mode: io.FileMode.WRITE,
bool flush: false,
}) async {
writeAsBytesSync(bytes, mode: mode, flush: flush);
return this;
}
@override
void writeAsBytesSync(
List<int> bytes, {
io.FileMode mode: io.FileMode.WRITE,
bool flush: false,
}) {
if (!_isWriteMode(mode)) {
throw common.badFileDescriptor(path);
}
_FileNode node = _resolvedBackingOrCreate;
_truncateIfNecessary(node, mode);
node.content.addAll(bytes);
node.touch();
}
@override
Future<File> writeAsString(
String contents, {
io.FileMode mode: io.FileMode.WRITE,
Encoding encoding: UTF8,
bool flush: false,
}) async {
writeAsStringSync(contents, mode: mode, encoding: encoding, flush: flush);
return this;
}
@override
void writeAsStringSync(
String contents, {
io.FileMode mode: io.FileMode.WRITE,
Encoding encoding: UTF8,
bool flush: false,
}) =>
writeAsBytesSync(encoding.encode(contents), mode: mode, flush: flush);
@override
File _clone(String path) => new _MemoryFile(fileSystem, path);
void _truncateIfNecessary(_FileNode node, io.FileMode mode) {
if (mode == io.FileMode.WRITE || mode == io.FileMode.WRITE_ONLY) {
node.content.clear();
}
}
@override
String toString() => "MemoryFile: '$path'";
}
/// Implementation of an [io.IOSink] that's backed by a [_FileNode].
class _FileSink implements io.IOSink {
final Future<_FileNode> _node;
final Completer<Null> _completer = new Completer<Null>();
Future<_FileNode> _pendingWrites;
Completer<Null> _streamCompleter;
bool _isClosed = false;
@override
Encoding encoding;
factory _FileSink.fromFile(
_MemoryFile file,
io.FileMode mode,
Encoding encoding,
) {
Future<_FileNode> node = new Future<_FileNode>.microtask(() {
_FileNode node = file._resolvedBackingOrCreate;
file._truncateIfNecessary(node, mode);
return node;
});
return new _FileSink._(node, encoding);
}
_FileSink._(this._node, this.encoding) {
_pendingWrites = _node;
}
bool get isStreaming => !(_streamCompleter?.isCompleted ?? true);
@override
void add(List<int> data) {
_checkNotStreaming();
if (!_isClosed) {
_addData(data);
}
}
@override
void write(Object obj) => add(encoding.encode(obj?.toString() ?? 'null'));
@override
void writeAll(Iterable<dynamic> objects, [String separator = ""]) {
bool firstIter = true;
for (dynamic obj in objects) {
if (!firstIter && separator != null) {
write(separator);
}
firstIter = false;
write(obj);
}
}
@override
void writeln([Object obj = '']) {
write(obj);
write('\n');
}
@override
void writeCharCode(int charCode) => write(new String.fromCharCode(charCode));
@override
void addError(dynamic error, [StackTrace stackTrace]) {
_checkNotStreaming();
_completer.completeError(error, stackTrace);
}
@override
Future<Null> addStream(Stream<List<int>> stream) {
_checkNotStreaming();
_streamCompleter = new Completer<Null>();
void finish() {
_streamCompleter.complete();
_streamCompleter = null;
}
stream.listen(
(List<int> data) => _addData(data),
cancelOnError: true,
onError: (dynamic error, StackTrace stackTrace) {
_completer.completeError(error, stackTrace);
finish();
},
onDone: finish,
);
return _streamCompleter.future;
}
@override
// TODO(tvolkert): Change to Future<Null> once Dart 1.22 is stable
Future<dynamic> flush() {
_checkNotStreaming();
return _pendingWrites;
}
@override
Future<Null> close() {
_checkNotStreaming();
if (!_isClosed) {
_isClosed = true;
_pendingWrites.then(
(_) => _completer.complete(),
onError: (dynamic error, StackTrace stackTrace) =>
_completer.completeError(error, stackTrace),
);
}
return _completer.future;
}
@override
Future<Null> get done => _completer.future;
void _addData(List<int> data) {
_pendingWrites = _pendingWrites.then((_FileNode node) {
node.content.addAll(data);
return node;
});
}
void _checkNotStreaming() {
if (isStreaming) {
throw new StateError('StreamSink is bound to a stream');
}
}
}