| // 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. |
| |
| import 'package:file/file.dart'; |
| import 'package:file/src/common.dart' as common; |
| import 'package:file/src/io.dart' as io; |
| |
| import 'common.dart'; |
| import 'node.dart'; |
| |
| /// Checks if `node.type` returns [io.FileSystemEntityType.FILE]. |
| bool isFile(Node node) => node?.type == io.FileSystemEntityType.FILE; |
| |
| /// Checks if `node.type` returns [io.FileSystemEntityType.DIRECTORY]. |
| bool isDirectory(Node node) => node?.type == io.FileSystemEntityType.DIRECTORY; |
| |
| /// Checks if `node.type` returns [io.FileSystemEntityType.LINK]. |
| bool isLink(Node node) => node?.type == io.FileSystemEntityType.LINK; |
| |
| /// Tells whether the specified path represents an absolute path. |
| bool isAbsolute(String path) => path.startsWith(separator); |
| |
| /// Validator function that is expected to throw a [FileSystemException] if |
| /// the node does not represent the type that is expected in any given context. |
| typedef void TypeChecker(Node node); |
| |
| /// Throws a [io.FileSystemException] if [node] is not a directory. |
| void checkIsDir(Node node, PathGenerator path) { |
| if (!isDirectory(node)) { |
| throw common.notADirectory(path()); |
| } |
| } |
| |
| /// Throws a [io.FileSystemException] if [expectedType] doesn't match |
| /// [actualType]. |
| void checkType( |
| FileSystemEntityType expectedType, |
| FileSystemEntityType actualType, |
| PathGenerator path, |
| ) { |
| if (expectedType != actualType) { |
| switch (expectedType) { |
| case FileSystemEntityType.DIRECTORY: |
| throw common.notADirectory(path()); |
| case FileSystemEntityType.FILE: |
| assert(actualType == FileSystemEntityType.DIRECTORY); |
| throw common.isADirectory(path()); |
| case FileSystemEntityType.LINK: |
| throw common.invalidArgument(path()); |
| default: |
| // Should not happen |
| throw new AssertionError(); |
| } |
| } |
| } |
| |
| /// Tells if the specified file mode represents a write mode. |
| bool isWriteMode(io.FileMode mode) => |
| mode == io.FileMode.WRITE || |
| mode == io.FileMode.APPEND || |
| mode == io.FileMode.WRITE_ONLY || |
| mode == io.FileMode.WRITE_ONLY_APPEND; |
| |
| /// Returns a [PathGenerator] that generates a subpath of the constituent |
| /// [parts] (from [start]..[end], inclusive). |
| PathGenerator subpath(List<String> parts, int start, int end) { |
| return () => parts.sublist(start, end + 1).join(separator); |
| } |
| |
| /// Tells whether the given string is empty. |
| bool isEmpty(String str) => str.isEmpty; |
| |
| /// Returns the node ultimately referred to by [link]. This will resolve |
| /// the link references (following chains of links as necessary) and return |
| /// the node at the end of the link chain. |
| /// |
| /// If a loop in the link chain is found, this will throw a |
| /// [FileSystemException], calling [path] to generate the path. |
| /// |
| /// If [ledger] is specified, the resolved path to the terminal node will be |
| /// appended to the ledger (or overwritten in the ledger if a link target |
| /// specified an absolute path). The path will not be normalized, meaning |
| /// `..` and `.` path segments may be present. |
| /// |
| /// If [tailVisitor] is specified, it will be invoked for the tail element of |
| /// the last link in the symbolic link chain, and its return value will be the |
| /// return value of this method (thus allowing callers to create the entity |
| /// at the end of the chain on demand). |
| Node resolveLinks( |
| LinkNode link, |
| PathGenerator path, { |
| List<String> ledger, |
| Node tailVisitor(DirectoryNode parent, String childName, Node child), |
| }) { |
| // Record a breadcrumb trail to guard against symlink loops. |
| Set<LinkNode> breadcrumbs = new Set<LinkNode>(); |
| |
| Node node = link; |
| while (isLink(node)) { |
| link = node; |
| if (!breadcrumbs.add(node)) { |
| throw common.tooManyLevelsOfSymbolicLinks(path()); |
| } |
| if (ledger != null) { |
| if (isAbsolute(link.target)) { |
| ledger.clear(); |
| } else if (ledger.isNotEmpty) { |
| ledger.removeLast(); |
| } |
| ledger.addAll(link.target.split(separator)); |
| } |
| node = link.getReferent( |
| tailVisitor: (DirectoryNode parent, String childName, Node child) { |
| if (tailVisitor != null && !isLink(child)) { |
| // Only invoke [tailListener] on the final resolution pass. |
| child = tailVisitor(parent, childName, child); |
| } |
| return child; |
| }, |
| ); |
| } |
| |
| return node; |
| } |