// 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(this._path) : _rawPath = FileSystemEntity._toUtf8Array(_path); _Directory.fromRawPath(Uint8List rawPath) : _rawPath = FileSystemEntity._toNullTerminatedUtf8Array(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 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 _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', ), }; 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 Uri.directory(path); } Future 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 FileSystemException("Exists failed", path, result); } return (result == 1); } Directory get absolute => Directory(_absolutePath); Future 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 => Directory(_systemTemp(_Namespace._namespace)); Future createTemp([String? prefix]) { prefix ??= ''; if (path == '') { throw 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 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 FileSystemException._fromOSError( result, "Creation of temporary directory failed", fullPrefix, ); } return Directory(result); } Future _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 rename(String newPath) { return _File._dispatchWithNamespace(_IOService.directoryRename, [ null, _rawPath, newPath, ]).then((response) { _checkForErrorResponse(response, "Rename failed", path); return Directory(newPath); }); } Directory renameSync(String newPath) { var result = _rename(_Namespace._namespace, _rawPath, newPath); if (result is OSError) { throw FileSystemException._fromOSError(result, "Rename failed", path); } return Directory(newPath); } Stream list({ bool recursive = false, bool followLinks = true, }) { return _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 listSync({ bool recursive = false, bool followLinks = true, }) { var result = []; _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'"; } 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 = StreamController(sync: true); bool canceled = false; bool nextRunning = false; bool closed = false; _AsyncDirectoryListerOps? _ops; Completer closeCompleter = 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 get stream => controller.stream; void onListen() { _File._dispatchWithNamespace(_IOService.directoryListStart, [ null, rawPath, recursive, followLinks, ]).then((response) { if (response is int) { _ops = _AsyncDirectoryListerOps._(response); next(); } else if (response is Error) { controller.addError(response, response.stackTrace); close(); } else { error(response as List); 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(File.fromRawPath(result[i])); break; case listDirectory: controller.add(Directory.fromRawPath(result[i])); break; case listLink: controller.add(Link.fromRawPath(result[i])); break; case listError: error(result[i]); break; case listDone: canceled = true; return; } } } else { controller.addError(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 message) { var errorResponseInfo = message[responseError]! as List; var errorType = errorResponseInfo[_errorResponseErrorType]; if (errorType == _illegalArgumentResponse) { controller.addError(ArgumentError()); } else if (errorType == _osErrorResponse) { var err = 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(FileSystemException("Internal error")); } } }