| // Copyright (c) 2013, 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 dart.io; |
| |
| /// The type of an entity on the file system, |
| /// such as a file, directory, or link. |
| /// |
| /// These constants are used by the [FileSystemEntity] class |
| /// to indicate the object's type. |
| class FileSystemEntityType { |
| static const file = const FileSystemEntityType._internal(0); |
| @Deprecated("Use file instead") |
| static const FILE = file; |
| |
| static const directory = const FileSystemEntityType._internal(1); |
| @Deprecated("Use directory instead") |
| static const DIRECTORY = directory; |
| |
| static const link = const FileSystemEntityType._internal(2); |
| @Deprecated("Use link instead") |
| static const LINK = link; |
| |
| static const notFound = const FileSystemEntityType._internal(3); |
| @Deprecated("Use notFound instead") |
| static const NOT_FOUND = notFound; |
| |
| static const _typeList = const [ |
| FileSystemEntityType.file, |
| FileSystemEntityType.directory, |
| FileSystemEntityType.link, |
| FileSystemEntityType.notFound, |
| ]; |
| final int _type; |
| |
| const FileSystemEntityType._internal(this._type); |
| |
| static FileSystemEntityType _lookup(int type) => _typeList[type]; |
| String toString() => const ['file', 'directory', 'link', 'notFound'][_type]; |
| } |
| |
| /// The result of calling the POSIX `stat()` function on a file system object. |
| /// |
| /// This is an immutable object, representing the snapshotted values returned |
| /// by the `stat()` call. |
| class FileStat { |
| // These must agree with enum FileStat in file.h. |
| static const _type = 0; |
| static const _changedTime = 1; |
| static const _modifiedTime = 2; |
| static const _accessedTime = 3; |
| static const _mode = 4; |
| static const _size = 5; |
| |
| static final _epoch = DateTime.fromMillisecondsSinceEpoch(0, isUtc: true); |
| static final _notFound = new FileStat._internal( |
| _epoch, _epoch, _epoch, FileSystemEntityType.notFound, 0, -1); |
| |
| /// The time of the last change to the data or metadata of the file system |
| /// object. |
| /// |
| /// On Windows platforms, this is instead the file creation time. |
| final DateTime changed; |
| |
| /// The time of the last change to the data of the file system object. |
| final DateTime modified; |
| |
| /// The time of the last access to the data of the file system object. |
| /// |
| /// On Windows platforms, this may have 1 day granularity, and be |
| /// out of date by an hour. |
| final DateTime accessed; |
| |
| /// The type of the underlying file system object. |
| /// |
| /// [FileSystemEntityType.notFound] if [stat] or [statSync] failed. |
| final FileSystemEntityType type; |
| |
| /// The mode of the file system object. |
| /// |
| /// Permissions are encoded in the lower 16 bits of this number, and can be |
| /// decoded using the [modeString] getter. |
| final int mode; |
| |
| /// The size of the file system object. |
| final int size; |
| |
| FileStat._internal(this.changed, this.modified, this.accessed, this.type, |
| this.mode, this.size); |
| |
| external static _statSync(_Namespace namespace, String path); |
| |
| /// Calls the operating system's `stat()` function (or equivalent) on [path]. |
| /// |
| /// If [path] is a symbolic link then it is resolved and results for the |
| /// resulting file are returned. |
| /// |
| /// Returns a [FileStat] object containing the data returned by `stat()`. |
| /// If the call fails, returns a [FileStat] object with [FileStat.type] set to |
| /// [FileSystemEntityType.notFound] and the other fields invalid. |
| static FileStat statSync(String path) { |
| final IOOverrides? overrides = IOOverrides.current; |
| if (overrides == null) { |
| return _statSyncInternal(path); |
| } |
| return overrides.statSync(path); |
| } |
| |
| static FileStat _statSyncInternal(String path) { |
| // Trailing path is not supported on Windows. |
| if (Platform.isWindows) { |
| path = FileSystemEntity._trimTrailingPathSeparators(path); |
| } |
| var data = _statSync(_Namespace._namespace, path); |
| if (data is OSError) return FileStat._notFound; |
| return new FileStat._internal( |
| new DateTime.fromMillisecondsSinceEpoch(data[_changedTime]), |
| new DateTime.fromMillisecondsSinceEpoch(data[_modifiedTime]), |
| new DateTime.fromMillisecondsSinceEpoch(data[_accessedTime]), |
| FileSystemEntityType._lookup(data[_type]), |
| data[_mode], |
| data[_size]); |
| } |
| |
| /// Asynchronously calls the operating system's `stat()` function (or |
| /// equivalent) on [path]. |
| /// |
| /// If [path] is a symbolic link then it is resolved and results for the |
| /// resulting file are returned. |
| /// |
| /// Returns a [Future] which completes with the same results as [statSync]. |
| static Future<FileStat> stat(String path) { |
| final IOOverrides? overrides = IOOverrides.current; |
| if (overrides == null) { |
| return _stat(path); |
| } |
| return overrides.stat(path); |
| } |
| |
| static Future<FileStat> _stat(String path) { |
| // Trailing path is not supported on Windows. |
| if (Platform.isWindows) { |
| path = FileSystemEntity._trimTrailingPathSeparators(path); |
| } |
| return _File._dispatchWithNamespace(_IOService.fileStat, [null, path]) |
| .then((response) { |
| if (_isErrorResponse(response)) { |
| return FileStat._notFound; |
| } |
| // Unwrap the real list from the "I'm not an error" wrapper. |
| List data = response[1]; |
| return new FileStat._internal( |
| new DateTime.fromMillisecondsSinceEpoch(data[_changedTime]), |
| new DateTime.fromMillisecondsSinceEpoch(data[_modifiedTime]), |
| new DateTime.fromMillisecondsSinceEpoch(data[_accessedTime]), |
| FileSystemEntityType._lookup(data[_type]), |
| data[_mode], |
| data[_size]); |
| }); |
| } |
| |
| String toString() => """ |
| FileStat: type $type |
| changed $changed |
| modified $modified |
| accessed $accessed |
| mode ${modeString()} |
| size $size"""; |
| |
| /// The mode value as a human-readable string. |
| /// |
| /// The string is in the format "rwxrwxrwx", reflecting the user, group, and |
| /// world permissions to read, write, and execute the file system object, with |
| /// "-" replacing the letter for missing permissions. Extra permission bits |
| /// may be represented by prepending "(suid)", "(guid)", and/or "(sticky)" to |
| /// the mode string. |
| String modeString() { |
| var permissions = mode & 0xFFF; |
| var codes = const ['---', '--x', '-w-', '-wx', 'r--', 'r-x', 'rw-', 'rwx']; |
| var result = []; |
| if ((permissions & 0x800) != 0) result.add("(suid) "); |
| if ((permissions & 0x400) != 0) result.add("(guid) "); |
| if ((permissions & 0x200) != 0) result.add("(sticky) "); |
| result |
| ..add(codes[(permissions >> 6) & 0x7]) |
| ..add(codes[(permissions >> 3) & 0x7]) |
| ..add(codes[permissions & 0x7]); |
| return result.join(); |
| } |
| } |
| |
| /// The common superclass of [File], [Directory], and [Link]. |
| /// |
| /// [FileSystemEntity] objects are returned from directory listing |
| /// operations. To determine whether a [FileSystemEntity] is a [File], a |
| /// [Directory], or a [Link] perform a type check: |
| /// ```dart |
| /// if (entity is File) (entity as File).readAsStringSync(); |
| /// ``` |
| /// You can also use the [type] or [typeSync] methods to determine |
| /// the type of a file system object. |
| /// |
| /// Most methods in this class exist both in synchronous and asynchronous |
| /// versions, for example, [exists] and [existsSync]. |
| /// Unless you have a specific reason for using the synchronous version |
| /// of a method, prefer the asynchronous version to avoid blocking your program. |
| /// |
| /// Here's the exists method in action: |
| /// ```dart |
| /// var isThere = await entity.exists(); |
| /// print(isThere ? 'exists' : 'non-existent'); |
| /// ``` |
| /// |
| /// ## Other resources |
| /// |
| /// * The [Files and directories](https://dart.dev/guides/libraries/library-tour#files-and-directories) |
| /// section of the library tour. |
| /// |
| /// * [Write Command-Line Apps](https://dart.dev/tutorials/server/cmdline), |
| /// a tutorial about writing command-line apps, includes information about |
| /// files and directories. |
| abstract class FileSystemEntity { |
| static const _backslashChar = 0x5c; |
| static const _slashChar = 0x2f; |
| static const _colonChar = 0x3a; |
| |
| String get _path; |
| Uint8List get _rawPath; |
| |
| String get path; |
| |
| /// A [Uri] representing the file system entity's location. |
| /// |
| /// The returned URI's scheme is always "file" if the entity's [path] is |
| /// absolute, otherwise the scheme will be empty and the URI relative. |
| Uri get uri => new Uri.file(path); |
| |
| /// Checks whether the file system entity with this path exists. |
| /// |
| /// Returns a `Future<bool>` that completes with the result. |
| /// |
| /// Since [FileSystemEntity] is abstract, every [FileSystemEntity] object |
| /// is actually an instance of one of the subclasses [File], |
| /// [Directory], and [Link]. Calling [exists] on an instance of one |
| /// of these subclasses checks whether the object exists in the file |
| /// system object exists *and* is of the correct type (file, directory, |
| /// or link). To check whether a path points to an object on the |
| /// file system, regardless of the object's type, use the [type] |
| /// static method. |
| Future<bool> exists(); |
| |
| /// Synchronously checks whether the file system entity with this path |
| /// exists. |
| /// |
| /// Since [FileSystemEntity] is abstract, every [FileSystemEntity] object |
| /// is actually an instance of one of the subclasses [File], |
| /// [Directory], and [Link]. Calling [existsSync] on an instance of |
| /// one of these subclasses checks whether the object exists in the |
| /// file system object exists and is of the correct type (file, |
| /// directory, or link). To check whether a path points to an object |
| /// on the file system, regardless of the object's type, use the |
| /// [typeSync] static method. |
| bool existsSync(); |
| |
| /// Renames this file system entity. |
| /// |
| /// Returns a `Future<FileSystemEntity>` that completes with a |
| /// [FileSystemEntity] instance for the renamed file system entity. |
| /// |
| /// If [newPath] identifies an existing entity of the same type, |
| /// that entity is removed first. |
| /// If [newPath] identifies an existing entity of a different type, |
| /// the operation fails and the future completes with an exception. |
| Future<FileSystemEntity> rename(String newPath); |
| |
| /// Synchronously renames this file system entity. |
| /// |
| /// Returns a [FileSystemEntity] instance for the renamed entity. |
| /// |
| /// If [newPath] identifies an existing entity of the same type, |
| /// that entity is removed first. |
| /// If [newPath] identifies an existing entity of a different type, |
| /// the operation fails and an exception is thrown. |
| FileSystemEntity renameSync(String newPath); |
| |
| /// Resolves the path of a file system object relative to the |
| /// current working directory. |
| /// |
| /// Resolves all symbolic links on the path and resolves all `..` and `.` path |
| /// segments. |
| /// |
| /// [resolveSymbolicLinks] uses the operating system's native |
| /// file system API to resolve the path, using the `realpath` function |
| /// on Linux and OS X, and the `GetFinalPathNameByHandle` function on |
| /// Windows. If the path does not point to an existing file system object, |
| /// `resolveSymbolicLinks` throws a `FileSystemException`. |
| /// |
| /// On Windows the `..` segments are resolved _before_ resolving the symbolic |
| /// link, and on other platforms the symbolic links are _resolved to their |
| /// target_ before applying a `..` that follows. |
| /// |
| /// To ensure the same behavior on all platforms resolve `..` segments before |
| /// calling `resolveSymbolicLinks`. One way of doing this is with the [Uri] |
| /// class: |
| /// ```dart |
| /// var path = Uri.parse('.').resolveUri(Uri.file(input)).toFilePath(); |
| /// if (path == '') path = '.'; |
| /// var resolved = await File(path).resolveSymbolicLinks(); |
| /// print(resolved); |
| /// ``` |
| /// since `Uri.resolve` removes `..` segments. This will result in the Windows |
| /// behavior. |
| Future<String> resolveSymbolicLinks() { |
| return _File._dispatchWithNamespace( |
| _IOService.fileResolveSymbolicLinks, [null, _rawPath]).then((response) { |
| if (_isErrorResponse(response)) { |
| throw _exceptionFromResponse( |
| response, "Cannot resolve symbolic links", path); |
| } |
| return response; |
| }); |
| } |
| |
| /// Resolves the path of a file system object relative to the |
| /// current working directory. |
| /// |
| /// Resolves all symbolic links on the path and resolves all `..` and `.` path |
| /// segments. |
| /// |
| /// [resolveSymbolicLinksSync] uses the operating system's native |
| /// file system API to resolve the path, using the `realpath` function |
| /// on linux and OS X, and the `GetFinalPathNameByHandle` function on |
| /// Windows. If the path does not point to an existing file system object, |
| /// `resolveSymbolicLinksSync` throws a `FileSystemException`. |
| /// |
| /// On Windows the `..` segments are resolved _before_ resolving the symbolic |
| /// link, and on other platforms the symbolic links are _resolved to their |
| /// target_ before applying a `..` that follows. |
| /// |
| /// To ensure the same behavior on all platforms resolve `..` segments before |
| /// calling `resolveSymbolicLinksSync`. One way of doing this is with the [Uri] |
| /// class: |
| /// ```dart |
| /// var path = Uri.parse('.').resolveUri(Uri.file(input)).toFilePath(); |
| /// if (path == '') path = '.'; |
| /// var resolved = File(path).resolveSymbolicLinksSync(); |
| /// print(resolved); |
| /// ``` |
| /// since `Uri.resolve` removes `..` segments. This will result in the Windows |
| /// behavior. |
| String resolveSymbolicLinksSync() { |
| var result = _resolveSymbolicLinks(_Namespace._namespace, _rawPath); |
| _throwIfError(result, "Cannot resolve symbolic links", path); |
| return result; |
| } |
| |
| /// Calls the operating system's `stat()` function on [path]. |
| /// |
| /// Identical to `FileStat.stat(this.path)`. |
| /// |
| /// Returns a `Future<FileStat>` object containing the data returned by |
| /// `stat()`. |
| /// |
| /// If [path] is a symbolic link then it is resolved and results for the |
| /// resulting file are returned. |
| /// |
| /// If the call fails, completes the future with a [FileStat] object |
| /// with `.type` set to [FileSystemEntityType.notFound] and the other fields |
| /// invalid. |
| Future<FileStat> stat() => FileStat.stat(path); |
| |
| /// Synchronously calls the operating system's `stat()` function on [path]. |
| /// |
| /// Identical to `FileStat.statSync(this.path)`. |
| /// |
| /// Returns a [FileStat] object containing the data returned by `stat()`. |
| /// |
| /// If [path] is a symbolic link then it is resolved and results for the |
| /// resulting file are returned. |
| /// |
| /// If the call fails, returns a [FileStat] object with `.type` set to |
| /// [FileSystemEntityType.notFound] and the other fields invalid. |
| FileStat statSync() => FileStat.statSync(path); |
| |
| /// Deletes this [FileSystemEntity]. |
| /// |
| /// If the [FileSystemEntity] is a directory, and if [recursive] is `false`, |
| /// the directory must be empty. Otherwise, if [recursive] is true, the |
| /// directory and all sub-directories and files in the directories are |
| /// deleted. Links are not followed when deleting recursively. Only the link |
| /// is deleted, not its target. |
| /// |
| /// If [recursive] is true, the [FileSystemEntity] is deleted even if the type |
| /// of the [FileSystemEntity] doesn't match the content of the file system. |
| /// This behavior allows [delete] to be used to unconditionally delete any file |
| /// system object. |
| /// |
| /// Returns a `Future<FileSystemEntity>` that completes with this |
| /// [FileSystemEntity] when the deletion is done. If the [FileSystemEntity] |
| /// cannot be deleted, the future completes with an exception. |
| Future<FileSystemEntity> delete({bool recursive = false}) => |
| _delete(recursive: recursive); |
| |
| /// Synchronously deletes this [FileSystemEntity]. |
| /// |
| /// If the [FileSystemEntity] is a directory, and if [recursive] is false, |
| /// the directory must be empty. Otherwise, if [recursive] is true, the |
| /// directory and all sub-directories and files in the directories are |
| /// deleted. Links are not followed when deleting recursively. Only the link |
| /// is deleted, not its target. |
| /// |
| /// If [recursive] is true, the [FileSystemEntity] is deleted even if the type |
| /// of the [FileSystemEntity] doesn't match the content of the file system. |
| /// This behavior allows [deleteSync] to be used to unconditionally delete any |
| /// file system object. |
| /// |
| /// Throws an exception if the [FileSystemEntity] cannot be deleted. |
| void deleteSync({bool recursive = false}) => |
| _deleteSync(recursive: recursive); |
| |
| /// Start watching the [FileSystemEntity] for changes. |
| /// |
| /// The implementation uses platform-dependent event-based APIs for receiving |
| /// file-system notifications, thus behavior depends on the platform. |
| /// |
| /// * `Windows`: Uses `ReadDirectoryChangesW`. The implementation only |
| /// supports watching directories. Recursive watching is supported. |
| /// * `Linux`: Uses `inotify`. The implementation supports watching both |
| /// files and directories. Recursive watching is not supported. |
| /// Note: When watching files directly, delete events might not happen |
| /// as expected. |
| /// * `OS X`: Uses `FSEvents`. The implementation supports watching both |
| /// files and directories. Recursive watching is supported. |
| /// |
| /// The system will start listening for events once the returned [Stream] is |
| /// being listened to, not when the call to [watch] is issued. |
| /// |
| /// The returned value is an endless broadcast [Stream], that only stops when |
| /// one of the following happens: |
| /// |
| /// * The [Stream] is canceled, e.g. by calling `cancel` on the |
| /// [StreamSubscription]. |
| /// * The [FileSystemEntity] being watched is deleted. |
| /// * System Watcher exits unexpectedly. e.g. On `Windows` this happens when |
| /// buffer that receive events from `ReadDirectoryChangesW` overflows. |
| /// |
| /// Use `events` to specify what events to listen for. The constants in |
| /// [FileSystemEvent] can be or'ed together to mix events. Default is |
| /// [FileSystemEvent.ALL]. |
| /// |
| /// A move event may be reported as separate delete and create events. |
| Stream<FileSystemEvent> watch( |
| {int events = FileSystemEvent.all, bool recursive = false}) { |
| // FIXME(bkonyi): find a way to do this using the raw path. |
| final String trimmedPath = _trimTrailingPathSeparators(path); |
| final IOOverrides? overrides = IOOverrides.current; |
| if (overrides == null) { |
| return _FileSystemWatcher._watch(trimmedPath, events, recursive); |
| } |
| return overrides.fsWatch(trimmedPath, events, recursive); |
| } |
| |
| Future<FileSystemEntity> _delete({bool recursive = false}); |
| void _deleteSync({bool recursive = false}); |
| |
| static Future<bool> _identical(String path1, String path2) { |
| return _File._dispatchWithNamespace( |
| _IOService.fileIdentical, [null, path1, path2]).then((response) { |
| if (_isErrorResponse(response)) { |
| throw _exceptionFromResponse(response, |
| "Error in FileSystemEntity.identical($path1, $path2)", ""); |
| } |
| return response; |
| }); |
| } |
| |
| /// Checks whether two paths refer to the same object in the |
| /// file system. |
| /// |
| /// Returns a `Future<bool>` that completes with the result. |
| /// |
| /// Comparing a link to its target returns `false`, as does comparing two links |
| /// that point to the same target. To check the target of a link, use |
| /// Link.target explicitly to fetch it. Directory links appearing |
| /// inside a path are followed, though, to find the file system object. |
| /// |
| /// Completes the returned Future with an error if one of the paths points |
| /// to an object that does not exist. |
| static Future<bool> identical(String path1, String path2) { |
| IOOverrides? overrides = IOOverrides.current; |
| if (overrides == null) { |
| return _identical(path1, path2); |
| } |
| return overrides.fseIdentical(path1, path2); |
| } |
| |
| static final RegExp _absoluteWindowsPathPattern = |
| new RegExp(r'^(?:\\\\|[a-zA-Z]:[/\\])'); |
| |
| /// Whether this object's path is absolute. |
| /// |
| /// An absolute path is independent of the current working |
| /// directory ([Directory.current]). |
| /// A non-absolute path must be interpreted relative to |
| /// the current working directory. |
| /// |
| /// On Windows, a path is absolute if it starts with `\\` |
| /// (two backslashes or representing a UNC path) or with a drive letter |
| /// between `a` and `z` (upper or lower case) followed by `:\` or `:/`. |
| /// The makes, for example, `\file.ext` a non-absolute path |
| /// because it depends on the current drive letter. |
| /// |
| /// On non-Windows, a path is absolute if it starts with `/`. |
| /// |
| /// If the path is not absolute, use [absolute] to get an entity |
| /// with an absolute path referencing the same object in the file system, |
| /// if possible. |
| bool get isAbsolute => _isAbsolute(path); |
| |
| static bool _isAbsolute(String path) { |
| if (Platform.isWindows) { |
| return path.startsWith(_absoluteWindowsPathPattern); |
| } else { |
| return path.startsWith('/'); |
| } |
| } |
| |
| /// A [FileSystemEntity] whose path is the absolute path of [path]. |
| /// |
| /// The type of the returned instance is the same as the type of |
| /// this entity. |
| /// |
| /// A file system entity with an already absolute path |
| /// (as reported by [isAbsolute]) is returned directly. |
| /// For a non-absolute path, the returned entity is absolute ([isAbsolute]) |
| /// *if possible*, but still refers to the same file system object. |
| FileSystemEntity get absolute; |
| |
| String get _absolutePath { |
| if (isAbsolute) return path; |
| if (Platform.isWindows) return _absoluteWindowsPath(path); |
| String current = Directory.current.path; |
| if (current.endsWith('/')) { |
| return '$current$path'; |
| } else { |
| return '$current${Platform.pathSeparator}$path'; |
| } |
| } |
| |
| /// The ASCII code of the Windows drive letter of [path], if any. |
| /// |
| /// Returns the ASCII code of the upper-cased drive letter of |
| /// the path of [path], if it has a drive letter (starts with `[a-zA-z]:`), |
| /// or `-1` if it has no drive letter. |
| static int _windowsDriveLetter(String path) { |
| if (path.isEmpty || !path.startsWith(':', 1)) return -1; |
| var first = path.codeUnitAt(0) & ~0x20; |
| if (first >= 0x41 && first <= 0x5b) return first; |
| return -1; |
| } |
| |
| /// The relative [path] converted to an absolute path. |
| static String _absoluteWindowsPath(String path) { |
| assert(Platform.isWindows); |
| assert(!_isAbsolute(path)); |
| // Could perhaps use something like |
| // https://docs.microsoft.com/en-us/windows/win32/api/pathcch/nf-pathcch-pathalloccombine |
| var current = Directory.current.path; |
| if (path.startsWith(r'\')) { |
| assert(!path.startsWith(r'\', 1)); |
| // Absolute path, no drive letter. |
| var currentDrive = _windowsDriveLetter(current); |
| if (currentDrive >= 0) { |
| return '${current[0]}:$path'; |
| } |
| // If `current` is a UNC path \\server\share[...], |
| // we make the absolute path relative to the share. |
| // Also works with `\\?\c:\` paths. |
| if (current.startsWith(r'\\')) { |
| var serverEnd = current.indexOf(r'\', 2); |
| if (serverEnd >= 0) { |
| // We may want to recognize UNC paths of the form: |
| // \\?\UNC\Server\share\... |
| // specially, and be relative to the *share* not to UNC\. |
| var shareEnd = current.indexOf(r'\', serverEnd + 1); |
| if (shareEnd < 0) shareEnd = current.length; |
| return '${current.substring(0, shareEnd)}$path'; |
| } |
| } |
| // If `current` is not in the drive-letter:path format, |
| // or not \\server\share[\path], |
| // we ignore it and return a relative path. |
| return path; |
| } |
| var entityDrive = _windowsDriveLetter(path); |
| if (entityDrive >= 0) { |
| if (entityDrive != _windowsDriveLetter(current)) { |
| // Need to resolve relative to current directory of the drive. |
| // Windows remembers the last CWD of each drive. |
| // We currently don't have that information, |
| // so we assume the root of that drive. |
| return '${path[0]}:\\$path'; |
| } |
| |
| /// A `c:relative\path` path on the same drive as `current`. |
| path = path.substring(2); |
| assert(!path.startsWith(r'\\')); |
| } |
| if (current.endsWith(r'\') || current.endsWith('/')) { |
| return '$current$path'; |
| } |
| return '$current\\$path'; |
| } |
| |
| static bool _identicalSync(String path1, String path2) { |
| var result = _identicalNative(_Namespace._namespace, path1, path2); |
| _throwIfError(result, 'Error in FileSystemEntity.identicalSync'); |
| return result; |
| } |
| |
| /// Synchronously checks whether two paths refer to the same object in the |
| /// file system. |
| /// |
| /// Comparing a link to its target returns `false`, as does comparing two links |
| /// that point to the same target. To check the target of a link, use |
| /// Link.target explicitly to fetch it. Directory links appearing |
| /// inside a path are followed, though, to find the file system object. |
| /// |
| /// Throws an error if one of the paths points to an object that does not |
| /// exist. |
| static bool identicalSync(String path1, String path2) { |
| IOOverrides? overrides = IOOverrides.current; |
| if (overrides == null) { |
| return _identicalSync(path1, path2); |
| } |
| return overrides.fseIdenticalSync(path1, path2); |
| } |
| |
| /// Test if [watch] is supported on the current system. |
| /// |
| /// OS X 10.6 and below is not supported. |
| static bool get isWatchSupported { |
| final IOOverrides? overrides = IOOverrides.current; |
| if (overrides == null) { |
| return _FileSystemWatcher.isSupported; |
| } |
| return overrides.fsWatchIsSupported(); |
| } |
| |
| // The native methods which determine type of the FileSystemEntity require |
| // that the buffer provided is null terminated. |
| static Uint8List _toUtf8Array(String s) => |
| _toNullTerminatedUtf8Array(utf8.encoder.convert(s)); |
| |
| static Uint8List _toNullTerminatedUtf8Array(Uint8List l) { |
| if (l.isEmpty || (l.isNotEmpty && l.last != 0)) { |
| final tmp = new Uint8List(l.length + 1); |
| tmp.setRange(0, l.length, l); |
| return tmp; |
| } else { |
| return l; |
| } |
| } |
| |
| static String _toStringFromUtf8Array(Uint8List l) { |
| Uint8List nonNullTerminated = l; |
| if (l.last == 0) { |
| nonNullTerminated = |
| new Uint8List.view(l.buffer, l.offsetInBytes, l.length - 1); |
| } |
| return utf8.decode(nonNullTerminated, allowMalformed: true); |
| } |
| |
| /// Finds the type of file system object that a path points to. |
| /// |
| /// Returns a `Future<FileSystemEntityType>` that completes with the same |
| /// results as [typeSync]. |
| static Future<FileSystemEntityType> type(String path, |
| {bool followLinks = true}) { |
| return _getType(_toUtf8Array(path), followLinks); |
| } |
| |
| /// Synchronously finds the type of file system object that a path points to. |
| /// |
| /// Returns [FileSystemEntityType.link] only if [followLinks] is `false` and if |
| /// [path] points to a link. |
| /// |
| /// Returns [FileSystemEntityType.notFound] if [path] does not point to a file |
| /// system object or if any other error occurs in looking up the path. |
| static FileSystemEntityType typeSync(String path, {bool followLinks = true}) { |
| return _getTypeSync(_toUtf8Array(path), followLinks); |
| } |
| |
| /// Whether [path] refers to a link. |
| /// |
| /// Checks whether `type(path, followLinks: false)` |
| /// returns [FileSystemEntityType.link]. |
| static Future<bool> isLink(String path) => _isLinkRaw(_toUtf8Array(path)); |
| |
| static Future<bool> _isLinkRaw(Uint8List rawPath) => _getType(rawPath, false) |
| .then((type) => (type == FileSystemEntityType.link)); |
| |
| /// Whether [path] refers to a file. |
| /// |
| /// Checks whether `type(path)` returns [FileSystemEntityType.file]. |
| static Future<bool> isFile(String path) => _getType(_toUtf8Array(path), true) |
| .then((type) => (type == FileSystemEntityType.file)); |
| |
| /// Whether [path]] refers to a directory. |
| /// |
| /// Checks whether `type(path)` returns [FileSystemEntityType.directory]. |
| static Future<bool> isDirectory(String path) => |
| _getType(_toUtf8Array(path), true) |
| .then((type) => (type == FileSystemEntityType.directory)); |
| |
| /// Synchronously checks whether [path] refers to a link. |
| /// |
| /// Checks whether `typeSync(path, followLinks: false)` returns |
| /// [FileSystemEntityType.link]. |
| static bool isLinkSync(String path) => _isLinkRawSync(_toUtf8Array(path)); |
| |
| static bool _isLinkRawSync(rawPath) => |
| (_getTypeSync(rawPath, false) == FileSystemEntityType.link); |
| |
| /// Synchronously checks whether [path] refers to a file. |
| /// |
| /// Checks whether `typeSync(path)` returns |
| /// [FileSystemEntityType.file]. |
| static bool isFileSync(String path) => |
| (_getTypeSync(_toUtf8Array(path), true) == FileSystemEntityType.file); |
| |
| /// Synchronously checks whether [path] refers to a directory. |
| /// |
| /// Checks whether `typeSync(path)` returns |
| /// [FileSystemEntityType.directory]. |
| static bool isDirectorySync(String path) => |
| (_getTypeSync(_toUtf8Array(path), true) == |
| FileSystemEntityType.directory); |
| |
| external static _getTypeNative( |
| _Namespace namespace, Uint8List rawPath, bool followLinks); |
| external static _identicalNative( |
| _Namespace namespace, String path1, String path2); |
| external static _resolveSymbolicLinks(_Namespace namespace, Uint8List path); |
| |
| // Finds the next-to-last component when dividing at path separators. |
| static final RegExp _parentRegExp = Platform.isWindows |
| ? new RegExp(r'[^/\\][/\\]+[^/\\]') |
| : new RegExp(r'[^/]/+[^/]'); |
| |
| /// The parent path of a path. |
| /// |
| /// Finds the final path component of a path, using the platform's |
| /// path separator to split the path, and returns the prefix up to |
| /// that part. |
| /// |
| /// Will not remove the root component of a Windows path, like "C:\\" or |
| /// "\\\\server_name\\". Includes a trailing path separator in the last |
| /// part of [path], and leaves no trailing path separator. |
| static String parentOf(String path) { |
| int rootEnd = -1; |
| if (Platform.isWindows) { |
| if (path.startsWith(_absoluteWindowsPathPattern)) { |
| // Root ends at first / or \ after the first two characters. |
| rootEnd = path.indexOf(new RegExp(r'[/\\]'), 2); |
| if (rootEnd == -1) return path; |
| } else if (path.startsWith('\\') || path.startsWith('/')) { |
| rootEnd = 0; |
| } |
| } else if (path.startsWith('/')) { |
| rootEnd = 0; |
| } |
| // Ignore trailing slashes. |
| // All non-trivial cases have separators between two non-separators. |
| int pos = path.lastIndexOf(_parentRegExp); |
| if (pos > rootEnd) { |
| return path.substring(0, pos + 1); |
| } else if (rootEnd > -1) { |
| return path.substring(0, rootEnd + 1); |
| } else { |
| return '.'; |
| } |
| } |
| |
| /// The parent directory of this entity. |
| Directory get parent => new Directory(parentOf(path)); |
| |
| static FileSystemEntityType _getTypeSyncHelper( |
| Uint8List rawPath, bool followLinks) { |
| var result = _getTypeNative(_Namespace._namespace, rawPath, followLinks); |
| _throwIfError(result, 'Error getting type of FileSystemEntity'); |
| return FileSystemEntityType._lookup(result); |
| } |
| |
| static FileSystemEntityType _getTypeSync( |
| Uint8List rawPath, bool followLinks) { |
| IOOverrides? overrides = IOOverrides.current; |
| if (overrides == null) { |
| return _getTypeSyncHelper(rawPath, followLinks); |
| } |
| return overrides.fseGetTypeSync( |
| utf8.decode(rawPath, allowMalformed: true), followLinks); |
| } |
| |
| static Future<FileSystemEntityType> _getTypeRequest( |
| Uint8List rawPath, bool followLinks) { |
| return _File._dispatchWithNamespace( |
| _IOService.fileType, [null, rawPath, followLinks]).then((response) { |
| if (_isErrorResponse(response)) { |
| throw _exceptionFromResponse(response, "Error getting type", |
| utf8.decode(rawPath, allowMalformed: true)); |
| } |
| return FileSystemEntityType._lookup(response); |
| }); |
| } |
| |
| static Future<FileSystemEntityType> _getType( |
| Uint8List rawPath, bool followLinks) { |
| IOOverrides? overrides = IOOverrides.current; |
| if (overrides == null) { |
| return _getTypeRequest(rawPath, followLinks); |
| } |
| return overrides.fseGetType( |
| utf8.decode(rawPath, allowMalformed: true), followLinks); |
| } |
| |
| static _throwIfError(Object result, String msg, [String? path]) { |
| if (result is OSError) { |
| throw new FileSystemException(msg, path, result); |
| } else if (result is ArgumentError) { |
| throw result; |
| } |
| } |
| |
| // TODO(bkonyi): find a way to do this with raw paths. |
| static String _trimTrailingPathSeparators(String path) { |
| // TODO(40614): Remove once non-nullability is sound. |
| ArgumentError.checkNotNull(path, "path"); |
| if (Platform.isWindows) { |
| while (path.length > 1 && |
| (path.endsWith(Platform.pathSeparator) || path.endsWith('/'))) { |
| path = path.substring(0, path.length - 1); |
| } |
| } else { |
| while (path.length > 1 && path.endsWith(Platform.pathSeparator)) { |
| path = path.substring(0, path.length - 1); |
| } |
| } |
| return path; |
| } |
| |
| // TODO(bkonyi): find a way to do this with raw paths. |
| static String _ensureTrailingPathSeparators(String path) { |
| if (path.isEmpty) path = '.'; |
| if (Platform.isWindows) { |
| while (!path.endsWith(Platform.pathSeparator) && !path.endsWith('/')) { |
| path = "$path${Platform.pathSeparator}"; |
| } |
| } else { |
| while (!path.endsWith(Platform.pathSeparator)) { |
| path = "$path${Platform.pathSeparator}"; |
| } |
| } |
| return path; |
| } |
| } |
| |
| /// Base event class emitted by [FileSystemEntity.watch]. |
| class FileSystemEvent { |
| /// Bitfield for [FileSystemEntity.watch], to enable [FileSystemCreateEvent]s. |
| static const int create = 1 << 0; |
| @Deprecated("Use create instead") |
| static const int CREATE = 1 << 0; |
| |
| /// Bitfield for [FileSystemEntity.watch], to enable [FileSystemModifyEvent]s. |
| static const int modify = 1 << 1; |
| @Deprecated("Use modify instead") |
| static const int MODIFY = 1 << 1; |
| |
| /// Bitfield for [FileSystemEntity.watch], to enable [FileSystemDeleteEvent]s. |
| static const int delete = 1 << 2; |
| @Deprecated("Use delete instead") |
| static const int DELETE = 1 << 2; |
| |
| /// Bitfield for [FileSystemEntity.watch], to enable [FileSystemMoveEvent]s. |
| static const int move = 1 << 3; |
| @Deprecated("Use move instead") |
| static const int MOVE = 1 << 3; |
| |
| /// Bitfield for [FileSystemEntity.watch], for enabling all of [create], |
| /// [modify], [delete] and [move]. |
| static const int all = create | modify | delete | move; |
| @Deprecated("Use all instead") |
| static const int ALL = create | modify | delete | move; |
| |
| static const int _modifyAttributes = 1 << 4; |
| static const int _deleteSelf = 1 << 5; |
| static const int _isDir = 1 << 6; |
| |
| /// The type of event. See [FileSystemEvent] for a list of events. |
| final int type; |
| |
| /// The path that triggered the event. |
| /// |
| /// Depending on the platform and the [FileSystemEntity], the path may be |
| /// relative. |
| final String path; |
| |
| /// Is `true` if the event target was a directory. |
| /// |
| /// Note that if the file has been deleted by the time the event has arrived, |
| /// this will always be `false` on Windows. In particular, it will always be |
| /// `false` for `delete` events. |
| final bool isDirectory; |
| |
| FileSystemEvent._(this.type, this.path, this.isDirectory); |
| } |
| |
| /// File system event for newly created file system objects. |
| class FileSystemCreateEvent extends FileSystemEvent { |
| FileSystemCreateEvent._(path, isDirectory) |
| : super._(FileSystemEvent.create, path, isDirectory); |
| |
| String toString() => "FileSystemCreateEvent('$path')"; |
| } |
| |
| /// File system event for modifications of file system objects. |
| class FileSystemModifyEvent extends FileSystemEvent { |
| /// If the content was changed and not only the attributes, [contentChanged] |
| /// is `true`. |
| final bool contentChanged; |
| |
| FileSystemModifyEvent._(path, isDirectory, this.contentChanged) |
| : super._(FileSystemEvent.modify, path, isDirectory); |
| |
| String toString() => |
| "FileSystemModifyEvent('$path', contentChanged=$contentChanged)"; |
| } |
| |
| /// File system event for deletion of file system objects. |
| class FileSystemDeleteEvent extends FileSystemEvent { |
| FileSystemDeleteEvent._(path, isDirectory) |
| : super._(FileSystemEvent.delete, path, isDirectory); |
| |
| String toString() => "FileSystemDeleteEvent('$path')"; |
| } |
| |
| /// File system event for moving of file system objects. |
| class FileSystemMoveEvent extends FileSystemEvent { |
| /// The destination path of the file being moved. |
| /// |
| /// The destination is `null` if the underlying implementation |
| /// is unable to identify the destination of the moved file. |
| /// |
| /// The source path is available as [path]. |
| final String? destination; |
| |
| FileSystemMoveEvent._(path, isDirectory, this.destination) |
| : super._(FileSystemEvent.move, path, isDirectory); |
| |
| String toString() { |
| var buffer = new StringBuffer(); |
| buffer.write("FileSystemMoveEvent('$path'"); |
| if (destination != null) buffer.write(", '$destination'"); |
| buffer.write(')'); |
| return buffer.toString(); |
| } |
| } |
| |
| abstract class _FileSystemWatcher { |
| external static Stream<FileSystemEvent> _watch( |
| String path, int events, bool recursive); |
| external static bool get isSupported; |
| } |