blob: 8cc103d52536adf713dd546e7aa3869bedf30cdd [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;
/// A class that represents the actual storage of an existent file system
/// entity (whereas classes [File], [Directory], and [Link] represent less
/// concrete entities that may or may not yet exist).
///
/// This data structure is loosely based on a Unix-style file system inode
/// (hence the name).
abstract class _Node {
_DirectoryNode _parent;
_Node(this._parent) {
if (_parent == null && !isRoot) {
throw new io.FileSystemException('All nodes must have a parent.');
}
}
/// Gets the directory that holds this node.
_DirectoryNode get parent => _parent;
/// Reparents this node to live in the specified directory.
set parent(_DirectoryNode parent) {
assert(parent != null);
_DirectoryNode ancestor = parent;
while (!ancestor.isRoot) {
if (ancestor == this) {
throw new io.FileSystemException(
'A directory cannot be its own ancestor.');
}
ancestor = ancestor.parent;
}
_parent = parent;
}
/// Returns the type of the file system entity that this node represents.
io.FileSystemEntityType get type;
/// Returns the POSIX stat information for this file system object.
io.FileStat get stat;
/// Returns the closest directory in the ancestry hierarchy starting with
/// this node. For directory nodes, it returns the node itself; for other
/// nodes, it returns the parent node.
_DirectoryNode get directory => _parent;
/// Tells if this node is a root node.
bool get isRoot => false;
// Returns the file system responsible for this node.
MemoryFileSystem get fs => _parent.fs;
}
/// Base class that represents the backing for those nodes that have
/// substance (namely, node types that will not redirect to other types when
/// you call [stat] on them).
abstract class _RealNode extends _Node {
int changed;
int modified;
int accessed;
int mode = 0x777;
_RealNode(_DirectoryNode parent) : super(parent) {
int now = new DateTime.now().millisecondsSinceEpoch;
changed = now;
modified = now;
accessed = now;
}
@override
io.FileStat get stat {
return new _MemoryFileStat(
new DateTime.fromMillisecondsSinceEpoch(changed),
new DateTime.fromMillisecondsSinceEpoch(modified),
new DateTime.fromMillisecondsSinceEpoch(accessed),
type,
mode,
size,
);
}
/// The size of the file system entity in bytes.
int get size;
}
/// Class that represents the backing for an in-memory directory.
class _DirectoryNode extends _RealNode {
final Map<String, _Node> children = <String, _Node>{};
_DirectoryNode(_DirectoryNode parent) : super(parent);
@override
io.FileSystemEntityType get type => io.FileSystemEntityType.DIRECTORY;
@override
_DirectoryNode get directory => this;
@override
int get size => 0;
}
/// Class that represents the backing for the root of the in-memory file system.
class _RootNode extends _DirectoryNode {
final MemoryFileSystem _fs;
_RootNode(this._fs) : super(null) {
assert(_fs != null);
assert(_fs._root == null);
}
@override
_DirectoryNode get parent => this;
@override
bool get isRoot => true;
@override
set parent(_DirectoryNode parent) => throw new UnsupportedError(
'Cannot set the parent of the root directory.');
@override
MemoryFileSystem get fs => _fs;
}
/// Class that represents the backing for an in-memory regular file.
class _FileNode extends _RealNode {
List<int> content = <int>[];
_FileNode(_DirectoryNode parent) : super(parent);
@override
io.FileSystemEntityType get type => io.FileSystemEntityType.FILE;
@override
int get size => content.length;
void copyFrom(_FileNode source) {
modified = changed = new DateTime.now().millisecondsSinceEpoch;
accessed = source.accessed;
mode = source.mode;
content = new List<int>.from(source.content);
}
}
/// Class that represents the backing for an in-memory symbolic link.
class _LinkNode extends _Node {
String target;
bool reentrant = false;
_LinkNode(_DirectoryNode parent, this.target) : super(parent) {
assert(target != null && target.isNotEmpty);
}
/// Gets the node backing for this link's target. Throws a
/// [FileSystemException] if this link references a non-existent file
/// system entity.
///
/// If [tailVisitor] is specified, it will be invoked for the tail path
/// segment of this link's target, and its return value will be used as the
/// return value of this method. If the tail path segment of this link's
/// target cannot be traversed into, a [FileSystemException] will be thrown,
/// and [tailVisitor] will not be invoked.
_Node getReferent({
_Node tailVisitor(_DirectoryNode parent, String childName, _Node child),
}) {
_Node referent = fs._findNode(
target,
reference: this,
segmentVisitor: (
_DirectoryNode parent,
String childName,
_Node child,
int currentSegment,
int finalSegment,
) {
if (tailVisitor != null && currentSegment == finalSegment) {
child = tailVisitor(parent, childName, child);
}
return child;
},
);
_checkExists(referent, () => target);
return referent;
}
/// Gets the node backing for this link's target, or null if this link
/// references a non-existent file system entity.
_Node get referentOrNull {
try {
return getReferent();
} on io.FileSystemException {
return null;
}
}
@override
io.FileSystemEntityType get type => io.FileSystemEntityType.LINK;
@override
io.FileStat get stat {
if (reentrant) {
return _MemoryFileStat._notFound;
}
reentrant = true;
try {
_Node node = referentOrNull;
return node == null ? _MemoryFileStat._notFound : node.stat;
} finally {
reentrant = false;
}
}
}