| // 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 { |
| String _path; |
| Uint8List _rawPath; |
| |
| _Directory(String path) { |
| if (path is! String) { |
| throw new ArgumentError('${Error.safeToString(path)} is not a String'); |
| } |
| _path = path; |
| _rawPath = FileSystemEntity._toUtf8Array(_path); |
| } |
| |
| _Directory.fromRawPath(Uint8List rawPath) { |
| if (rawPath == null) { |
| throw new ArgumentError('rawPath cannot be null'); |
| } |
| _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<FileSystemEntity> list, |
| Uint8List rawPath, |
| bool recursive, |
| bool followLinks); |
| |
| static Directory get current { |
| var result = _current(_Namespace._namespace); |
| if (result is OSError) { |
| throw new FileSystemException( |
| "Getting current working directory failed", "", result); |
| } |
| return new _Directory(result); |
| } |
| |
| static void set current(path) { |
| Uint8List _rawPath; |
| if (path is _Directory) { |
| // For our internal Directory implementation, go ahead and use the raw |
| // path. |
| _rawPath = path._rawPath; |
| } else if (path is Directory) { |
| // FIXME(bkonyi): package:file passes in instances of classes which do |
| // not have _path defined, so we will fallback to using the existing |
| // path String for now. |
| _rawPath = FileSystemEntity._toUtf8Array(path.path); |
| } else if (path is String) { |
| _rawPath = FileSystemEntity._toUtf8Array(path); |
| } else { |
| throw new 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 new FileSystemException( |
| "Setting current working directory failed", path, result); |
| } |
| } |
| |
| Uri get uri { |
| return new Uri.directory(path); |
| } |
| |
| Future<bool> exists() { |
| return _File._dispatchWithNamespace( |
| _IOService.directoryExists, [null, _rawPath]).then((response) { |
| if (_isErrorResponse(response)) { |
| throw _exceptionOrErrorFromResponse(response, "Exists failed"); |
| } |
| 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) { |
| if (_isErrorResponse(response)) { |
| throw _exceptionOrErrorFromResponse(response, "Creation failed"); |
| } |
| 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 new FileSystemException("Creation failed", path, result); |
| } |
| } |
| |
| static Directory get systemTemp => |
| new Directory(_systemTemp(_Namespace._namespace)); |
| |
| Future<Directory> createTemp([String prefix]) { |
| if (prefix == null) 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) { |
| if (_isErrorResponse(response)) { |
| throw _exceptionOrErrorFromResponse( |
| response, "Creation of temporary directory failed"); |
| } |
| return new Directory(response); |
| }); |
| } |
| |
| Directory createTempSync([String prefix]) { |
| if (prefix == null) 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( |
| "Creation of temporary directory failed", fullPrefix, result); |
| } |
| return new Directory(result); |
| } |
| |
| Future<Directory> _delete({bool recursive: false}) { |
| return _File._dispatchWithNamespace(_IOService.directoryDelete, |
| [null, _rawPath, recursive]).then((response) { |
| if (_isErrorResponse(response)) { |
| throw _exceptionOrErrorFromResponse(response, "Deletion failed"); |
| } |
| return this; |
| }); |
| } |
| |
| void _deleteSync({bool recursive: false}) { |
| var result = _deleteNative(_Namespace._namespace, _rawPath, recursive); |
| if (result is OSError) { |
| throw new FileSystemException("Deletion failed", path, result); |
| } |
| } |
| |
| Future<Directory> rename(String newPath) { |
| return _File._dispatchWithNamespace( |
| _IOService.directoryRename, [null, _rawPath, newPath]).then((response) { |
| if (_isErrorResponse(response)) { |
| throw _exceptionOrErrorFromResponse(response, "Rename failed"); |
| } |
| return new Directory(newPath); |
| }); |
| } |
| |
| Directory renameSync(String newPath) { |
| if (newPath is! String) { |
| throw new ArgumentError(); |
| } |
| var result = _rename(_Namespace._namespace, _rawPath, newPath); |
| if (result is OSError) { |
| throw new FileSystemException("Rename failed", path, result); |
| } |
| 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}) { |
| if (recursive is! bool || followLinks is! bool) { |
| throw new ArgumentError(); |
| } |
| 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'"; |
| |
| bool _isErrorResponse(response) => |
| response is List && response[0] != _successResponse; |
| |
| _exceptionOrErrorFromResponse(response, String message) { |
| assert(_isErrorResponse(response)); |
| switch (response[_errorResponseErrorType]) { |
| case _illegalArgumentResponse: |
| return new ArgumentError(); |
| case _osErrorResponse: |
| var err = new OSError(response[_osErrorResponseMessage], |
| response[_osErrorResponseErrorCode]); |
| return new FileSystemException(message, path, err); |
| default: |
| return new Exception("Unknown error"); |
| } |
| } |
| } |
| |
| 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; |
| |
| StreamController<FileSystemEntity> controller; |
| bool canceled = false; |
| bool nextRunning = false; |
| bool closed = false; |
| _AsyncDirectoryListerOps _ops; |
| Completer closeCompleter = new Completer(); |
| |
| _AsyncDirectoryLister(this.rawPath, this.recursive, this.followLinks) { |
| controller = new StreamController<FileSystemEntity>( |
| onListen: onListen, onResume: onResume, onCancel: onCancel, sync: true); |
| } |
| |
| // 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 == null) ? null : _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); |
| 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(message) { |
| var errorType = message[responseError][_errorResponseErrorType]; |
| if (errorType == _illegalArgumentResponse) { |
| controller.addError(new ArgumentError()); |
| } else if (errorType == _osErrorResponse) { |
| var responseErrorInfo = message[responseError]; |
| var err = new OSError(responseErrorInfo[_osErrorResponseMessage], |
| responseErrorInfo[_osErrorResponseErrorCode]); |
| var errorPath = message[responsePath]; |
| if (errorPath == null) { |
| errorPath = utf8.decode(rawPath, allowMalformed: true); |
| } else if (errorPath is Uint8List) { |
| errorPath = utf8.decode(message[responsePath], allowMalformed: true); |
| } |
| controller.addError( |
| new FileSystemException("Directory listing failed", errorPath, err)); |
| } else { |
| controller.addError(new FileSystemException("Internal error")); |
| } |
| } |
| } |