blob: dfe23f9de482f35dfca9802671e462e1e4afc404 [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;
/// Validator function for use with `_renameSync`. This will be invoked if the
/// rename would overwrite an existing entity at the new path. If this operation
/// should not be allowed, this function is expected to throw a
/// [io.FileSystemException]. The lack of such an exception will be interpreted
/// as the overwrite being permissible.
typedef void _RenameOverwriteValidator<T extends _Node>(T existingNode);
/// Base class for all in-memory file system entity types.
abstract class _MemoryFileSystemEntity implements FileSystemEntity {
@override
final MemoryFileSystem fileSystem;
@override
final String path;
_MemoryFileSystemEntity(this.fileSystem, this.path);
@override
String get dirname => fileSystem.path.dirname(path);
@override
String get basename => fileSystem.path.basename(path);
/// Returns the expected type of this entity, which may differ from the type
/// of the node that's found at the path specified by this entity.
io.FileSystemEntityType get expectedType;
/// Gets the node that backs this file system entity, or null if this
/// entity does not exist.
_Node get _backingOrNull {
try {
return fileSystem._findNode(path);
} on io.FileSystemException {
return null;
}
}
/// Gets the node that backs this file system entity. Throws a
/// [io.FileSystemException] if this entity doesn't exist.
///
/// The type of the node is not guaranteed to match [expectedType].
_Node get _backing {
_Node node = fileSystem._findNode(path);
_checkExists(node, () => path);
return node;
}
/// Gets the node that backs this file system entity, or if that node is
/// a symbolic link, the target node. This also will check that the type of
/// the node (after symlink resolution) matches [expectedType]. If the type
/// doesn't match, this will throw a [io.FileSystemException].
_Node get _resolvedBacking {
_Node node = _backing;
node = _isLink(node) ? _resolveLinks(node, () => path) : node;
_checkType(expectedType, node.type, () => path);
return node;
}
/// Checks the expected type of this file system entity against the specified
/// node's `stat` type, throwing a [FileSystemException] if the types don't
/// match. Note that since this checks the node's `stat` type, symbolic links
/// will be resolved to their target type for the purpose of this validation.
///
/// Protected methods that accept a `checkType` argument will default to this
/// method if the `checkType` argument is unspecified.
void _defaultCheckType(_Node node) {
_checkType(expectedType, node.stat.type, () => path);
}
@override
Uri get uri => new Uri.file(path);
@override
Future<bool> exists() async => existsSync();
@override
Future<String> resolveSymbolicLinks() async => resolveSymbolicLinksSync();
@override
String resolveSymbolicLinksSync() {
List<String> ledger = <String>[];
if (isAbsolute) {
ledger.add('');
}
_Node node = fileSystem._findNode(path,
pathWithSymlinks: ledger, followTailLink: true);
_checkExists(node, () => path);
String resolved = ledger.join(_separator);
if (!_isAbsolute(resolved)) {
resolved = fileSystem._cwd + _separator + resolved;
}
return fileSystem.path.normalize(resolved);
}
@override
Future<io.FileStat> stat() => fileSystem.stat(path);
@override
io.FileStat statSync() => fileSystem.statSync(path);
@override
Future<FileSystemEntity> delete({bool recursive: false}) async {
deleteSync(recursive: recursive);
return this;
}
@override
void deleteSync({bool recursive: false}) => _deleteSync(recursive: recursive);
@override
Stream<io.FileSystemEvent> watch({
int events: io.FileSystemEvent.ALL,
bool recursive: false,
}) =>
throw new UnsupportedError('Watching not supported in MemoryFileSystem');
@override
bool get isAbsolute => _isAbsolute(path);
@override
FileSystemEntity get absolute {
String absolutePath = path;
if (!_isAbsolute(absolutePath)) {
absolutePath = fileSystem.path.join(fileSystem._cwd, absolutePath);
}
return _clone(absolutePath);
}
@override
Directory get parent => new _MemoryDirectory(fileSystem, dirname);
/// Helper method for subclasses wishing to synchronously create this entity.
/// This method will traverse the path to this entity one segment at a time,
/// calling [createChild] for each segment whose child does not already exist.
///
/// When [createChild] is invoked:
/// - `parent` will be the parent node for the current segment and is
/// guaranteed to be non-null.
/// - `isFinalSegment` will indicate whether the current segment is the tail
/// segment, which in turn indicates that this is the segment into which to
/// create the node for this entity.
///
/// This method returns with the backing node for the entity at this [path].
/// If an entity already existed at this path, [createChild] will not be
/// invoked at all, and this method will return with the backing node for the
/// existing entity (whose type may differ from this entity's type).
///
/// If [followTailLink] is true and the result node is a link, this will
/// resolve it to its target prior to returning it.
_Node _createSync({
_Node createChild(_DirectoryNode parent, bool isFinalSegment),
bool followTailLink: false,
bool visitLinks: false,
}) {
return fileSystem._findNode(
path,
followTailLink: followTailLink,
visitLinks: visitLinks,
segmentVisitor: (
_DirectoryNode parent,
String childName,
_Node child,
int currentSegment,
int finalSegment,
) {
if (child == null) {
assert(!parent.children.containsKey(childName));
child = createChild(parent, currentSegment == finalSegment);
if (child != null) {
parent.children[childName] = child;
}
}
return child;
},
);
}
/// Helper method for subclasses wishing to synchronously rename this entity.
/// This method will look for an existing file system entity at the location
/// identified by [newPath], and if it finds an existing entity, it will check
/// the following:
///
/// - If the entity is of a different type than this entity, the operation
/// will fail, and a [io.FileSystemException] will be thrown.
/// - If the caller has specified [validateOverwriteExistingEntity], then that
/// method will be invoked and passed the node backing of the existing
/// entity that would overwritten by the rename action. That callback is
/// expected to throw a [io.FileSystemException] if overwriting the existing
/// entity is not allowed.
///
/// If the previous two checks pass, or if there was no existing entity at
/// the specified location, this will perform the rename.
///
/// If [newPath] cannot be traversed to because its directory does not exist,
/// a [io.FileSystemException] will be thrown.
///
/// If [followTailLink] is true and there is an existing link at the location
/// identified by [newPath], this will resolve the link to its target prior
/// to running the validation checks above.
///
/// If [checkType] is specified, it will be used to validate that the file
/// system entity that exists at [path] is of the expected type. By default,
/// [_defaultCheckType] is used to perform this validation.
FileSystemEntity _renameSync(
String newPath, {
_RenameOverwriteValidator<dynamic> validateOverwriteExistingEntity,
bool followTailLink: false,
_TypeChecker checkType,
}) {
_Node node = _backing;
(checkType ?? _defaultCheckType)(node);
fileSystem._findNode(
newPath,
segmentVisitor: (
_DirectoryNode parent,
String childName,
_Node child,
int currentSegment,
int finalSegment,
) {
if (currentSegment == finalSegment) {
if (child != null) {
if (followTailLink) {
FileSystemEntityType childType = child.stat.type;
if (childType != FileSystemEntityType.NOT_FOUND) {
_checkType(expectedType, child.stat.type, () => newPath);
}
} else {
_checkType(expectedType, child.type, () => newPath);
}
if (validateOverwriteExistingEntity != null) {
validateOverwriteExistingEntity(child);
}
parent.children.remove(childName);
}
node.parent.children.remove(basename);
parent.children[childName] = node;
node.parent = parent;
}
return child;
},
);
return _clone(newPath);
}
/// Deletes this entity from the node tree.
///
/// If [checkType] is specified, it will be used to validate that the file
/// system entity that exists at [path] is of the expected type. By default,
/// [_defaultCheckType] is used to perform this validation.
void _deleteSync({
bool recursive: false,
_TypeChecker checkType,
}) {
_Node node = _backing;
if (!recursive) {
if (node is _DirectoryNode && node.children.isNotEmpty) {
throw common.directoryNotEmpty(path);
}
(checkType ?? _defaultCheckType)(node);
}
// Once we remove this reference, the node and all its children will be
// garbage collected; we don't need to explicitly delete all children in
// the recursive:true case.
node.parent.children.remove(basename);
}
/// Creates a new entity with the same type as this entity but with the
/// specified path.
FileSystemEntity _clone(String path);
}