// 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. final class FileSystemEntityType { static const file = const FileSystemEntityType._internal(0); static const directory = const FileSystemEntityType._internal(1); static const link = const FileSystemEntityType._internal(2); static const unixDomainSock = const FileSystemEntityType._internal(3); static const pipe = const FileSystemEntityType._internal(4); static const notFound = const FileSystemEntityType._internal(5); @Deprecated("Use notFound instead") static const NOT_FOUND = notFound; static const _typeList = const [ FileSystemEntityType.file, FileSystemEntityType.directory, FileSystemEntityType.link, FileSystemEntityType.unixDomainSock, FileSystemEntityType.pipe, FileSystemEntityType.notFound, ]; final int _type; const FileSystemEntityType._internal(this._type); static FileSystemEntityType _lookup(int type) => _typeList[type]; String toString() => const [ 'file', 'directory', 'link', 'unixDomainSock', 'pipe', '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. interface 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! Int64List) return FileStat._notFound; return FileStat._internal( DateTime.fromMillisecondsSinceEpoch(data[_changedTime]), DateTime.fromMillisecondsSinceEpoch(data[_modifiedTime]), 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 stat(String path) { final IOOverrides? overrides = IOOverrides.current; if (overrides == null) { return _stat(path); } return overrides.stat(path); } static Future _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 (response is List && response[0] != _successResponse) { return FileStat._notFound; } // Unwrap the real list from the "I'm not an error" wrapper. var data = (response as List)[1] as List; return FileStat._internal( DateTime.fromMillisecondsSinceEpoch(data[_changedTime] as int), DateTime.fromMillisecondsSinceEpoch(data[_modifiedTime] as int), DateTime.fromMillisecondsSinceEpoch(data[_accessedTime] as int), FileSystemEntityType._lookup(data[_type] as int), data[_mode] as int, data[_size] as int, ); }, ); } 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' : 'nonexistent'); /// ``` /// /// ## 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` 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 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` that completes with a /// [FileSystemEntity] instance for the renamed file system entity. Future rename(String newPath); /// Synchronously renames this file system entity. /// /// Returns a [FileSystemEntity] instance for the renamed entity. 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. /// /// On Windows, attempting to resolve a symbolic link where the link type /// does not match the type of the resolved file system object will cause the /// Future to complete with a [PathAccessException] error. Future resolveSymbolicLinks() { return _File._dispatchWithNamespace(_IOService.fileResolveSymbolicLinks, [ null, _rawPath, ]).then((response) { _checkForErrorResponse(response, "Cannot resolve symbolic links", path); return response as String; }); } /// 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. /// /// On Windows, a symbolic link is created as either a file link or a /// directory link. Attempting to resolve such a symbolic link requires the /// link type to match the type of the file system object that it points to, /// otherwise it throws a [PathAccessException]. 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` 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 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]. /// /// The exact details vary according to the [FileSystemEntity]: /// /// * [Directory.delete] /// * [File.delete] /// * [Link.delete] Future delete({bool recursive = false}) => _delete(recursive: recursive); /// Synchronously deletes this [FileSystemEntity]. /// /// The exact details vary according to the [FileSystemEntity]: /// /// * [Directory.deleteSync] /// * [File.deleteSync] /// * [Link.deleteSync] 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 the /// [File System Events API](https://developer.apple.com/library/archive/documentation/Darwin/Conceptual/FSEvents_ProgGuide/TechnologyOverview/TechnologyOverview.html). /// The implementation supports watching both files and directories. /// Recursive watching is supported. /// This API has several limitations: /// /// * Changes that occurred shortly *before* the [watch] method was /// called may still appear in the [Stream]. /// * Changes that occur in a short period of time may arrive /// out-of-order. /// * Multiple changes made in a single directory may be coalesced into /// a single `FileSystemEvent`. /// /// 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 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 _delete({bool recursive = false}); void _deleteSync({bool recursive = false}); static Future _identical(String path1, String path2) { return _File._dispatchWithNamespace(_IOService.fileIdentical, [ null, path1, path2, ]).then((response) { _checkForErrorResponse( response, "Error in FileSystemEntity.identical($path1, $path2)", "", ); return response as bool; }); } /// Checks whether two paths refer to the same object in the /// file system. /// /// Returns a `Future` 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 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 `:/`. /// This 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.encode(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` that completes with the same /// results as [typeSync]. static Future 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.notFound] if [path] does not point to a file /// system object or if any other error occurs in looking up the path. /// /// If [path] points to a link and [followLinks] is `true` then the result /// will be for the file system object that the link points to. If that /// object does not exist then the result will be /// [FileSystemEntityType.notFound]. If [path] points to a link and /// [followLinks] is `false` then the result will be /// [FileSystemEntityType.link]. 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 isLink(String path) => _isLinkRaw(_toUtf8Array(path)); static Future _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 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 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); static bool _isDirectoryIgnoringLinksSync(String path) => (_getTypeSync(_toUtf8Array(path), false) == 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( _toStringFromUtf8Array(rawPath), followLinks, ); } static Future _getTypeRequest( Uint8List rawPath, bool followLinks, ) { return _File._dispatchWithNamespace(_IOService.fileType, [ null, rawPath, followLinks, ]).then((response) { _checkForErrorResponse( response, "Error getting type", utf8.decode(rawPath, allowMalformed: true), ); return FileSystemEntityType._lookup(response as int); }); } static Future _getType( Uint8List rawPath, bool followLinks, ) { IOOverrides? overrides = IOOverrides.current; if (overrides == null) { return _getTypeRequest(rawPath, followLinks); } return overrides.fseGetType(_toStringFromUtf8Array(rawPath), 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) { if (Platform.isWindows) { // "C:" and "C:\" are semantically different on Windows and only "C:\" is an // absolute path. // See https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats final minimumCharToNotTrim = _isAbsolute(path) ? 3 : 1; while (path.length > minimumCharToNotTrim && (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]. sealed class FileSystemEvent { /// Bitfield for [FileSystemEntity.watch], to enable [FileSystemCreateEvent]s. static const int create = 1 << 0; /// Bitfield for [FileSystemEntity.watch], to enable [FileSystemModifyEvent]s. static const int modify = 1 << 1; /// Bitfield for [FileSystemEntity.watch], to enable [FileSystemDeleteEvent]s. static const int delete = 1 << 2; /// Bitfield for [FileSystemEntity.watch], to enable [FileSystemMoveEvent]s. 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; static const int _modifyAttributes = 1 << 4; static const int _deleteSelf = 1 << 5; static const int _isDir = 1 << 6; static const int _movedTo = 1 << 7; /// 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; /// Whether the event target is a directory. /// /// The value will always be `false` for [FileSystemDeleteEvent]. /// /// On Windows `isDirectory` is computed by checking the file system entity /// type after the event, which means it can be incorrect. It can be incorrectly /// `false` for a directory create, modify or move event if that directory was /// deleted soon after the event occurred. And, it can be incorrectly `true` /// for a newly created link to a directory. final bool isDirectory; FileSystemEvent._(this.type, this.path, this.isDirectory); } /// File system event for newly created file system objects. final class FileSystemCreateEvent extends FileSystemEvent { /// Constructs a new [FileSystemCreateEvent]. FileSystemCreateEvent(String path, bool isDirectory) : super._(FileSystemEvent.create, path, isDirectory); String toString() => "FileSystemCreateEvent('$path', isDirectory=$isDirectory)"; } /// File system event for modifications of file system objects. final class FileSystemModifyEvent extends FileSystemEvent { /// If the content was changed and not only the attributes, [contentChanged] /// is `true`. final bool contentChanged; /// Constructs a new [FileSystemModifyEvent]. FileSystemModifyEvent(String path, bool isDirectory, this.contentChanged) : super._(FileSystemEvent.modify, path, isDirectory); String toString() => "FileSystemModifyEvent('$path', isDirectory=$isDirectory, " "contentChanged=$contentChanged)"; } /// File system event for deletion of file system objects. final class FileSystemDeleteEvent extends FileSystemEvent { /// Constructs a new [FileSystemDeleteEvent]. FileSystemDeleteEvent(String path, bool isDirectory) : super._(FileSystemEvent.delete, path, false); String toString() => "FileSystemDeleteEvent('$path')"; /// Whether the file system object was a directory. /// /// The value will always be `false` for [FileSystemDeleteEvent]. @Deprecated('always false for FileSystemDeleteEvent') bool get isDirectory => false; } /// File system event for moving of file system objects. final 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; /// Constructs a new [FileSystemMoveEvent]. FileSystemMoveEvent(String path, bool isDirectory, this.destination) : super._(FileSystemEvent.move, path, isDirectory); String toString() => "FileSystemMoveEvent('$path', " "isDirectory=$isDirectory, destination=$destination)"; } abstract class _FileSystemWatcher { external static Stream _watch( String path, int events, bool recursive, ); external static bool get isSupported; }