| // Copyright (c) 2012, 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; |
| |
| class _Directory extends FileSystemEntity implements Directory { |
| final String _path; |
| final Uint8List _rawPath; |
| |
| _Directory(String path) |
| : _path = _checkNotNull(path, "path"), |
| _rawPath = FileSystemEntity._toUtf8Array(path); |
| |
| _Directory.fromRawPath(Uint8List rawPath) |
| : _rawPath = FileSystemEntity._toNullTerminatedUtf8Array( |
| _checkNotNull(rawPath, "rawPath")), |
| _path = FileSystemEntity._toStringFromUtf8Array(rawPath); |
| |
| String get path => _path; |
| |
| external static _current(_Namespace namespace); |
| external static _setCurrent(_Namespace namespace, Uint8List rawPath); |
| external static _createTemp(_Namespace namespace, Uint8List rawPath); |
| external static String _systemTemp(_Namespace namespace); |
| external static _exists(_Namespace namespace, Uint8List rawPath); |
| external static _create(_Namespace namespace, Uint8List rawPath); |
| external static _deleteNative( |
| _Namespace namespace, Uint8List rawPath, bool recursive); |
| external static _rename( |
| _Namespace namespace, Uint8List rawPath, String newPath); |
| external static void _fillWithDirectoryListing( |
| _Namespace namespace, |
| List<FileSystemEntity> list, |
| Uint8List rawPath, |
| bool recursive, |
| bool followLinks); |
| |
| static Directory get current { |
| var result = _current(_Namespace._namespace); |
| if (result is OSError) { |
| throw FileSystemException._fromOSError( |
| result, "Getting current working directory failed", ""); |
| } |
| return new _Directory(result); |
| } |
| |
| static void set current(Object? path) { |
| var _rawPath = switch (path) { |
| // For our internal Directory implementation, go ahead and use the raw |
| // path. |
| _Directory d => d._rawPath, |
| // Fall back to the String-based path. |
| Directory d => FileSystemEntity._toUtf8Array(d.path), |
| String s => FileSystemEntity._toUtf8Array(s), |
| _ => throw ArgumentError('${Error.safeToString(path)} is not a String or' |
| ' Directory') |
| }; |
| |
| if (!_EmbedderConfig._mayChdir) { |
| throw new UnsupportedError( |
| "This embedder disallows setting Directory.current"); |
| } |
| var result = _setCurrent(_Namespace._namespace, _rawPath); |
| if (result is ArgumentError) throw result; |
| if (result is OSError) { |
| throw FileSystemException._fromOSError( |
| result, "Setting current working directory failed", path.toString()); |
| } |
| } |
| |
| Uri get uri { |
| return new Uri.directory(path); |
| } |
| |
| Future<bool> exists() { |
| return _File._dispatchWithNamespace( |
| _IOService.directoryExists, [null, _rawPath]).then((response) { |
| _checkForErrorResponse(response, "Exists failed", path); |
| return response == 1; |
| }); |
| } |
| |
| bool existsSync() { |
| var result = _exists(_Namespace._namespace, _rawPath); |
| if (result is OSError) { |
| throw new FileSystemException("Exists failed", path, result); |
| } |
| return (result == 1); |
| } |
| |
| Directory get absolute => new Directory(_absolutePath); |
| |
| Future<Directory> create({bool recursive = false}) { |
| if (recursive) { |
| return exists().then((exists) { |
| if (exists) return this; |
| if (path != parent.path) { |
| return parent.create(recursive: true).then((_) { |
| return create(); |
| }); |
| } else { |
| return create(); |
| } |
| }); |
| } else { |
| return _File._dispatchWithNamespace( |
| _IOService.directoryCreate, [null, _rawPath]).then((response) { |
| _checkForErrorResponse(response, "Creation failed", path); |
| return this; |
| }); |
| } |
| } |
| |
| void createSync({bool recursive = false}) { |
| if (recursive) { |
| if (existsSync()) return; |
| if (path != parent.path) { |
| parent.createSync(recursive: true); |
| } |
| } |
| var result = _create(_Namespace._namespace, _rawPath); |
| if (result is OSError) { |
| throw FileSystemException._fromOSError(result, "Creation failed", path); |
| } |
| } |
| |
| static Directory get systemTemp => |
| new Directory(_systemTemp(_Namespace._namespace)); |
| |
| Future<Directory> createTemp([String? prefix]) { |
| prefix ??= ''; |
| if (path == '') { |
| throw new ArgumentError("Directory.createTemp called with an empty path. " |
| "To use the system temp directory, use Directory.systemTemp"); |
| } |
| String fullPrefix; |
| // FIXME(bkonyi): here we're using `path` directly, which might cause |
| // issues if it is not UTF-8 encoded. |
| if (path.endsWith('/') || (Platform.isWindows && path.endsWith('\\'))) { |
| fullPrefix = "$path$prefix"; |
| } else { |
| fullPrefix = "$path${Platform.pathSeparator}$prefix"; |
| } |
| return _File._dispatchWithNamespace(_IOService.directoryCreateTemp, |
| [null, FileSystemEntity._toUtf8Array(fullPrefix)]).then((response) { |
| _checkForErrorResponse( |
| response, "Creation of temporary directory failed", path); |
| return Directory(response as String); |
| }); |
| } |
| |
| Directory createTempSync([String? prefix]) { |
| prefix ??= ''; |
| if (path == '') { |
| throw new ArgumentError("Directory.createTemp called with an empty path. " |
| "To use the system temp directory, use Directory.systemTemp"); |
| } |
| String fullPrefix; |
| // FIXME(bkonyi): here we're using `path` directly, which might cause |
| // issues if it is not UTF-8 encoded. |
| if (path.endsWith('/') || (Platform.isWindows && path.endsWith('\\'))) { |
| fullPrefix = "$path$prefix"; |
| } else { |
| fullPrefix = "$path${Platform.pathSeparator}$prefix"; |
| } |
| var result = _createTemp( |
| _Namespace._namespace, FileSystemEntity._toUtf8Array(fullPrefix)); |
| if (result is OSError) { |
| throw new FileSystemException._fromOSError( |
| result, "Creation of temporary directory failed", fullPrefix); |
| } |
| return new Directory(result); |
| } |
| |
| Future<Directory> _delete({bool recursive = false}) { |
| return _File._dispatchWithNamespace( |
| _IOService.directoryDelete, [null, _rawPath, recursive]) |
| .then((response) { |
| _checkForErrorResponse(response, "Deletion failed", path); |
| return this; |
| }); |
| } |
| |
| void _deleteSync({bool recursive = false}) { |
| var result = _deleteNative(_Namespace._namespace, _rawPath, recursive); |
| if (result is OSError) { |
| throw FileSystemException._fromOSError(result, "Deletion failed", path); |
| } |
| } |
| |
| Future<Directory> rename(String newPath) { |
| return _File._dispatchWithNamespace( |
| _IOService.directoryRename, [null, _rawPath, newPath]).then((response) { |
| _checkForErrorResponse(response, "Rename failed", path); |
| return new Directory(newPath); |
| }); |
| } |
| |
| Directory renameSync(String newPath) { |
| // TODO(40614): Remove once non-nullability is sound. |
| ArgumentError.checkNotNull(newPath, "newPath"); |
| var result = _rename(_Namespace._namespace, _rawPath, newPath); |
| if (result is OSError) { |
| throw FileSystemException._fromOSError(result, "Rename failed", path); |
| } |
| return new Directory(newPath); |
| } |
| |
| Stream<FileSystemEntity> list( |
| {bool recursive = false, bool followLinks = true}) { |
| return new _AsyncDirectoryLister( |
| // FIXME(bkonyi): here we're using `path` directly, which might cause issues |
| // if it is not UTF-8 encoded. |
| FileSystemEntity._toUtf8Array( |
| FileSystemEntity._ensureTrailingPathSeparators(path)), |
| recursive, |
| followLinks) |
| .stream; |
| } |
| |
| List<FileSystemEntity> listSync( |
| {bool recursive = false, bool followLinks = true}) { |
| // TODO(40614): Remove once non-nullability is sound. |
| ArgumentError.checkNotNull(recursive, "recursive"); |
| ArgumentError.checkNotNull(followLinks, "followLinks"); |
| var result = <FileSystemEntity>[]; |
| _fillWithDirectoryListing( |
| _Namespace._namespace, |
| result, |
| // FIXME(bkonyi): here we're using `path` directly, which might cause issues |
| // if it is not UTF-8 encoded. |
| FileSystemEntity._toUtf8Array( |
| FileSystemEntity._ensureTrailingPathSeparators(path)), |
| recursive, |
| followLinks); |
| return result; |
| } |
| |
| String toString() => "Directory: '$path'"; |
| |
| // TODO(40614): Remove once non-nullability is sound. |
| static T _checkNotNull<T>(T t, String name) { |
| ArgumentError.checkNotNull(t, name); |
| return t; |
| } |
| } |
| |
| abstract class _AsyncDirectoryListerOps { |
| external factory _AsyncDirectoryListerOps(int pointer); |
| |
| int? getPointer(); |
| } |
| |
| class _AsyncDirectoryLister { |
| static const int listFile = 0; |
| static const int listDirectory = 1; |
| static const int listLink = 2; |
| static const int listError = 3; |
| static const int listDone = 4; |
| |
| static const int responseType = 0; |
| static const int responsePath = 1; |
| static const int responseComplete = 1; |
| static const int responseError = 2; |
| |
| final Uint8List rawPath; |
| final bool recursive; |
| final bool followLinks; |
| |
| final controller = new StreamController<FileSystemEntity>(sync: true); |
| bool canceled = false; |
| bool nextRunning = false; |
| bool closed = false; |
| _AsyncDirectoryListerOps? _ops; |
| Completer closeCompleter = new Completer(); |
| |
| _AsyncDirectoryLister(this.rawPath, this.recursive, this.followLinks) { |
| controller |
| ..onListen = onListen |
| ..onResume = onResume |
| ..onCancel = onCancel; |
| } |
| |
| // WARNING: |
| // Calling this function will increase the reference count on the native |
| // object that implements the async directory lister operations. It should |
| // only be called to pass the pointer to the IO Service, which will decrement |
| // the reference count when it is finished with it. |
| int? _pointer() { |
| return _ops?.getPointer(); |
| } |
| |
| Stream<FileSystemEntity> get stream => controller.stream; |
| |
| void onListen() { |
| _File._dispatchWithNamespace(_IOService.directoryListStart, |
| [null, rawPath, recursive, followLinks]).then((response) { |
| if (response is int) { |
| _ops = new _AsyncDirectoryListerOps(response); |
| next(); |
| } else if (response is Error) { |
| controller.addError(response, response.stackTrace); |
| close(); |
| } else { |
| error(response as List<Object?>); |
| close(); |
| } |
| }); |
| } |
| |
| void onResume() { |
| if (!nextRunning) { |
| next(); |
| } |
| } |
| |
| Future onCancel() { |
| canceled = true; |
| // If we are active, but not requesting, close. |
| if (!nextRunning) { |
| close(); |
| } |
| |
| return closeCompleter.future; |
| } |
| |
| void next() { |
| if (canceled) { |
| close(); |
| return; |
| } |
| if (controller.isPaused || nextRunning) { |
| return; |
| } |
| var pointer = _pointer(); |
| if (pointer == null) { |
| return; |
| } |
| nextRunning = true; |
| _IOService._dispatch(_IOService.directoryListNext, [pointer]) |
| .then((result) { |
| nextRunning = false; |
| if (result is List) { |
| next(); |
| assert(result.length % 2 == 0); |
| for (int i = 0; i < result.length; i++) { |
| assert(i % 2 == 0); |
| switch (result[i++]) { |
| case listFile: |
| controller.add(new File.fromRawPath(result[i])); |
| break; |
| case listDirectory: |
| controller.add(new Directory.fromRawPath(result[i])); |
| break; |
| case listLink: |
| controller.add(new Link.fromRawPath(result[i])); |
| break; |
| case listError: |
| error(result[i]); |
| break; |
| case listDone: |
| canceled = true; |
| return; |
| } |
| } |
| } else { |
| controller.addError(new FileSystemException("Internal error")); |
| } |
| }); |
| } |
| |
| void _cleanup() { |
| controller.close(); |
| closeCompleter.complete(); |
| _ops = null; |
| } |
| |
| void close() { |
| if (closed) { |
| return; |
| } |
| if (nextRunning) { |
| return; |
| } |
| closed = true; |
| |
| var pointer = _pointer(); |
| if (pointer == null) { |
| _cleanup(); |
| } else { |
| _IOService._dispatch(_IOService.directoryListStop, [pointer]) |
| .whenComplete(_cleanup); |
| } |
| } |
| |
| void error(List<Object?> message) { |
| var errorResponseInfo = message[responseError]! as List<Object?>; |
| var errorType = errorResponseInfo[_errorResponseErrorType]; |
| if (errorType == _illegalArgumentResponse) { |
| controller.addError(new ArgumentError()); |
| } else if (errorType == _osErrorResponse) { |
| var err = new OSError( |
| errorResponseInfo[_osErrorResponseMessage] as String, |
| errorResponseInfo[_osErrorResponseErrorCode] as int); |
| var errorPath = message[responsePath]; |
| if (errorPath == null) { |
| errorPath = utf8.decode(rawPath, allowMalformed: true); |
| } else if (errorPath is Uint8List) { |
| errorPath = utf8.decode(errorPath, allowMalformed: true); |
| } |
| controller.addError(FileSystemException._fromOSError( |
| err, "Directory listing failed", errorPath as String)); |
| } else { |
| controller.addError(new FileSystemException("Internal error")); |
| } |
| } |
| } |