| // 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; |
| |
| // Read the file in blocks of size 64k. |
| const int _blockSize = 64 * 1024; |
| |
| // The maximum number of bytes to read in a single call to `read`. |
| // |
| // On Windows and macOS, it is an error to call |
| // `read/_read(fildes, buf, nbyte)` with `nbyte >= INT_MAX`. |
| // |
| // The POSIX specification states that the behavior of `read` is |
| // implementation-defined if `nbyte > SSIZE_MAX`. On Linux, the `read` will |
| // transfer at most 0x7ffff000 bytes and return the number of bytes actually. |
| // transfered. |
| // |
| // A smaller value has the advantage of: |
| // 1. making vm-service clients (e.g. debugger) more responsive |
| // 2. reducing memory overhead (since `readInto` copies memory) |
| // |
| // A bigger value reduces the number of system calls. |
| const int _maxReadSize = 16 * 1024 * 1024; // 16MB. |
| |
| class _FileStream extends Stream<List<int>> { |
| // Stream controller. |
| late StreamController<Uint8List> _controller; |
| |
| // Information about the underlying file. |
| String? _path; |
| RandomAccessFile? _openedFile; |
| int _position; |
| int? _end; |
| final Completer _closeCompleter = new Completer(); |
| |
| // Has the stream been paused or unsubscribed? |
| bool _unsubscribed = false; |
| |
| // Is there a read currently in progress? |
| bool _readInProgress = true; |
| bool _closed = false; |
| |
| bool _atEnd = false; |
| |
| _FileStream(this._path, int? position, this._end) : _position = position ?? 0; |
| |
| _FileStream.forStdin() : _position = 0; |
| |
| _FileStream.forRandomAccessFile(RandomAccessFile f) |
| : _position = 0, |
| _openedFile = f; |
| |
| StreamSubscription<Uint8List> listen( |
| void onData(Uint8List event)?, { |
| Function? onError, |
| void onDone()?, |
| bool? cancelOnError, |
| }) { |
| _controller = new StreamController<Uint8List>( |
| sync: true, |
| onListen: _start, |
| onResume: _readBlock, |
| onCancel: () { |
| _unsubscribed = true; |
| return _closeFile(); |
| }, |
| ); |
| return _controller.stream.listen( |
| onData, |
| onError: onError, |
| onDone: onDone, |
| cancelOnError: cancelOnError, |
| ); |
| } |
| |
| Future _closeFile() { |
| if (_readInProgress || _closed) { |
| return _closeCompleter.future; |
| } |
| _closed = true; |
| |
| void done() { |
| _closeCompleter.complete(); |
| _controller.close(); |
| } |
| |
| _openedFile!.close().catchError(_controller.addError).whenComplete(done); |
| return _closeCompleter.future; |
| } |
| |
| void _readBlock() { |
| // Don't start a new read if one is already in progress. |
| if (_readInProgress) return; |
| if (_atEnd) { |
| _closeFile(); |
| return; |
| } |
| _readInProgress = true; |
| int readBytes = _blockSize; |
| final end = _end; |
| if (end != null) { |
| readBytes = min(readBytes, end - _position); |
| if (readBytes < 0) { |
| _readInProgress = false; |
| if (!_unsubscribed) { |
| _controller.addError(new RangeError("Bad end position: $end")); |
| _closeFile(); |
| _unsubscribed = true; |
| } |
| return; |
| } |
| } |
| _openedFile! |
| .read(readBytes) |
| .then((block) { |
| _readInProgress = false; |
| if (_unsubscribed) { |
| _closeFile(); |
| return; |
| } |
| _position += block.length; |
| // read() may return less than `readBytes` if `_openFile` is a pipe or |
| // terminal or if a signal is received. Only a empty return indicates |
| // that the write side of the pipe is closed or that we are at the end |
| // of a file. |
| // See https://man7.org/linux/man-pages/man2/read.2.html |
| if (block.length == 0 || (_end != null && _position == _end)) { |
| _atEnd = true; |
| } |
| if (!_atEnd && !_controller.isPaused) { |
| _readBlock(); |
| } |
| if (block.length > 0) { |
| _controller.add(block); |
| } |
| if (_atEnd) { |
| _closeFile(); |
| } |
| }) |
| .catchError((e, s) { |
| if (!_unsubscribed) { |
| _controller.addError(e, s); |
| _closeFile(); |
| _unsubscribed = true; |
| } |
| }); |
| } |
| |
| void _start() { |
| if (_position < 0) { |
| _controller.addError(new RangeError("Bad start position: $_position")); |
| _controller.close(); |
| _closeCompleter.complete(); |
| return; |
| } |
| |
| void onReady(RandomAccessFile file) { |
| _openedFile = file; |
| _readInProgress = false; |
| _readBlock(); |
| } |
| |
| void onOpenFile(RandomAccessFile file) { |
| if (_position > 0) { |
| file |
| .setPosition(_position) |
| .then( |
| onReady, |
| onError: (e, s) { |
| _controller.addError(e, s); |
| _readInProgress = false; |
| _closeFile(); |
| }, |
| ); |
| } else { |
| onReady(file); |
| } |
| } |
| |
| void openFailed(error, stackTrace) { |
| _controller.addError(error, stackTrace); |
| _controller.close(); |
| _closeCompleter.complete(); |
| } |
| |
| final path = _path; |
| final openedFile = _openedFile; |
| if (openedFile != null) { |
| onOpenFile(openedFile); |
| } else if (path != null) { |
| new File( |
| path, |
| ).open(mode: FileMode.read).then(onOpenFile, onError: openFailed); |
| } else { |
| try { |
| onOpenFile(_File._openStdioSync(0)); |
| } catch (e, s) { |
| openFailed(e, s); |
| } |
| } |
| } |
| } |
| |
| class _FileStreamConsumer implements StreamConsumer<List<int>> { |
| File? _file; |
| Future<RandomAccessFile> _openFuture; |
| |
| _FileStreamConsumer(File file, FileMode mode) |
| : _file = file, |
| _openFuture = file.open(mode: mode); |
| |
| _FileStreamConsumer._fromStdio(int fd) |
| : _openFuture = new Future.value(_File._openStdioSync(fd)); |
| |
| _FileStreamConsumer.fromRandomAccessFile(RandomAccessFile f) |
| : _openFuture = Future.value(f); |
| |
| Future<File?> addStream(Stream<List<int>> stream) { |
| Completer<File?> completer = new Completer<File?>.sync(); |
| _openFuture |
| .then((openedFile) { |
| late StreamSubscription<List<int>> _subscription; |
| void error(e, StackTrace stackTrace) { |
| _subscription.cancel(); |
| openedFile.close(); |
| completer.completeError(e, stackTrace); |
| } |
| |
| _subscription = stream.listen( |
| (d) { |
| _subscription.pause(); |
| try { |
| openedFile |
| .writeFrom(d, 0, d.length) |
| .then((_) => _subscription.resume(), onError: error); |
| } catch (e, stackTrace) { |
| error(e, stackTrace); |
| } |
| }, |
| onDone: () { |
| completer.complete(_file); |
| }, |
| onError: error, |
| cancelOnError: true, |
| ); |
| }) |
| .catchError(completer.completeError); |
| return completer.future; |
| } |
| |
| Future<File?> close() => |
| _openFuture.then((openedFile) => openedFile.close()).then((_) => _file); |
| } |
| |
| // Class for encapsulating the native implementation of files. |
| class _File extends FileSystemEntity implements File { |
| final String _path; |
| final Uint8List _rawPath; |
| |
| _File(String path) |
| : _path = _checkNotNull(path, "path"), |
| _rawPath = FileSystemEntity._toUtf8Array(path); |
| |
| _File.fromRawPath(Uint8List rawPath) |
| : _rawPath = FileSystemEntity._toNullTerminatedUtf8Array( |
| _checkNotNull(rawPath, "rawPath"), |
| ), |
| _path = FileSystemEntity._toStringFromUtf8Array(rawPath); |
| |
| String get path => _path; |
| |
| // WARNING: |
| // Calling this function will increase the reference count on the native |
| // namespace object. It should only be called to pass the pointer to the |
| // IOService, which will decrement the reference count when it is finished |
| // with it. |
| static int _namespacePointer() => _Namespace._namespacePointer; |
| |
| static Future<Object?> _dispatchWithNamespace(int request, List data) { |
| data[0] = _namespacePointer(); |
| return _IOService._dispatch(request, data); |
| } |
| |
| Future<bool> exists() { |
| return _dispatchWithNamespace(_IOService.fileExists, [null, _rawPath]).then( |
| (response) { |
| _checkForErrorResponse(response, "Cannot check existence", path); |
| return response as bool; |
| }, |
| ); |
| } |
| |
| external static _exists(_Namespace namespace, Uint8List rawPath); |
| |
| bool existsSync() { |
| var result = _exists(_Namespace._namespace, _rawPath); |
| throwIfError(result, "Cannot check existence of file", path); |
| return result; |
| } |
| |
| File get absolute => new File(_absolutePath); |
| |
| Future<File> create({bool recursive = false, bool exclusive = false}) { |
| var result = recursive |
| ? parent.create(recursive: true) |
| : new Future.value(null); |
| return result |
| .then( |
| (_) => _dispatchWithNamespace(_IOService.fileCreate, [ |
| null, |
| _rawPath, |
| exclusive, |
| ]), |
| ) |
| .then((response) { |
| _checkForErrorResponse(response, "Cannot create file", path); |
| return this; |
| }); |
| } |
| |
| external static _create( |
| _Namespace namespace, |
| Uint8List rawPath, |
| bool exclusive, |
| ); |
| |
| external static _createLink( |
| _Namespace namespace, |
| Uint8List rawPath, |
| String target, |
| ); |
| |
| external static List<dynamic> _createPipe(_Namespace namespace); |
| |
| external static _linkTarget(_Namespace namespace, Uint8List rawPath); |
| |
| void createSync({bool recursive = false, bool exclusive = false}) { |
| if (recursive) { |
| parent.createSync(recursive: true); |
| } |
| var result = _create(_Namespace._namespace, _rawPath, exclusive); |
| throwIfError(result, "Cannot create file", path); |
| } |
| |
| Future<File> _delete({bool recursive = false}) { |
| if (recursive) { |
| return new Directory(path).delete(recursive: true).then((_) => this); |
| } |
| return _dispatchWithNamespace(_IOService.fileDelete, [null, _rawPath]).then( |
| (response) { |
| _checkForErrorResponse(response, "Cannot delete file", path); |
| return this; |
| }, |
| ); |
| } |
| |
| external static _deleteNative(_Namespace namespace, Uint8List rawPath); |
| |
| external static _deleteLinkNative(_Namespace namespace, Uint8List rawPath); |
| |
| void _deleteSync({bool recursive = false}) { |
| if (recursive) { |
| return new Directory.fromRawPath(_rawPath).deleteSync(recursive: true); |
| } |
| var result = _deleteNative(_Namespace._namespace, _rawPath); |
| throwIfError(result, "Cannot delete file", path); |
| } |
| |
| Future<File> rename(String newPath) { |
| return _dispatchWithNamespace(_IOService.fileRename, [ |
| null, |
| _rawPath, |
| newPath, |
| ]).then((response) { |
| _checkForErrorResponse( |
| response, |
| "Cannot rename file to '$newPath'", |
| path, |
| ); |
| return new File(newPath); |
| }); |
| } |
| |
| external static _rename( |
| _Namespace namespace, |
| Uint8List oldPath, |
| String newPath, |
| ); |
| |
| external static _renameLink( |
| _Namespace namespace, |
| Uint8List oldPath, |
| String newPath, |
| ); |
| |
| File renameSync(String newPath) { |
| var result = _rename(_Namespace._namespace, _rawPath, newPath); |
| throwIfError(result, "Cannot rename file to '$newPath'", path); |
| return new File(newPath); |
| } |
| |
| Future<File> copy(String newPath) { |
| return _dispatchWithNamespace(_IOService.fileCopy, [ |
| null, |
| _rawPath, |
| newPath, |
| ]).then((response) { |
| _checkForErrorResponse(response, "Cannot copy file to '$newPath'", path); |
| return new File(newPath); |
| }); |
| } |
| |
| external static _copy( |
| _Namespace namespace, |
| Uint8List oldPath, |
| String newPath, |
| ); |
| |
| File copySync(String newPath) { |
| var result = _copy(_Namespace._namespace, _rawPath, newPath); |
| throwIfError(result, "Cannot copy file to '$newPath'", path); |
| return new File(newPath); |
| } |
| |
| Future<RandomAccessFile> open({FileMode mode = FileMode.read}) { |
| if (mode != FileMode.read && |
| mode != FileMode.write && |
| mode != FileMode.append && |
| mode != FileMode.writeOnly && |
| mode != FileMode.writeOnlyAppend) { |
| return new Future.error( |
| new ArgumentError('Invalid file mode for this operation'), |
| ); |
| } |
| return _dispatchWithNamespace(_IOService.fileOpen, [ |
| null, |
| _rawPath, |
| mode._mode, |
| ]).then((response) { |
| _checkForErrorResponse(response, "Cannot open file", path); |
| return _RandomAccessFile(response as int, path); |
| }); |
| } |
| |
| Future<int> length() { |
| return _dispatchWithNamespace(_IOService.fileLengthFromPath, [ |
| null, |
| _rawPath, |
| ]).then((response) { |
| _checkForErrorResponse(response, "Cannot retrieve length of file", path); |
| return response as int; |
| }); |
| } |
| |
| external static _lengthFromPath(_Namespace namespace, Uint8List rawPath); |
| |
| int lengthSync() { |
| var result = _lengthFromPath(_Namespace._namespace, _rawPath); |
| throwIfError(result, "Cannot retrieve length of file", path); |
| return result; |
| } |
| |
| Future<DateTime> lastAccessed() { |
| return _dispatchWithNamespace(_IOService.fileLastAccessed, [ |
| null, |
| _rawPath, |
| ]).then((response) { |
| _checkForErrorResponse(response, "Cannot retrieve access time", path); |
| return DateTime.fromMillisecondsSinceEpoch(response as int); |
| }); |
| } |
| |
| external static _lastAccessed(_Namespace namespace, Uint8List rawPath); |
| |
| DateTime lastAccessedSync() { |
| var ms = _lastAccessed(_Namespace._namespace, _rawPath); |
| throwIfError(ms, "Cannot retrieve access time", path); |
| return new DateTime.fromMillisecondsSinceEpoch(ms); |
| } |
| |
| Future setLastAccessed(DateTime time) { |
| int millis = time.millisecondsSinceEpoch; |
| return _dispatchWithNamespace(_IOService.fileSetLastAccessed, [ |
| null, |
| _rawPath, |
| millis, |
| ]).then((response) { |
| _checkForErrorResponse(response, "Cannot set access time", path); |
| return null; |
| }); |
| } |
| |
| external static _setLastAccessed( |
| _Namespace namespace, |
| Uint8List rawPath, |
| int millis, |
| ); |
| |
| void setLastAccessedSync(DateTime time) { |
| int millis = time.millisecondsSinceEpoch; |
| var result = _setLastAccessed(_Namespace._namespace, _rawPath, millis); |
| if (result is OSError) { |
| throw new FileSystemException( |
| "Failed to set file access time", |
| path, |
| result, |
| ); |
| } |
| } |
| |
| Future<DateTime> lastModified() { |
| return _dispatchWithNamespace(_IOService.fileLastModified, [ |
| null, |
| _rawPath, |
| ]).then((response) { |
| _checkForErrorResponse( |
| response, |
| "Cannot retrieve modification time", |
| path, |
| ); |
| return DateTime.fromMillisecondsSinceEpoch(response as int); |
| }); |
| } |
| |
| external static _lastModified(_Namespace namespace, Uint8List rawPath); |
| |
| DateTime lastModifiedSync() { |
| var ms = _lastModified(_Namespace._namespace, _rawPath); |
| throwIfError(ms, "Cannot retrieve modification time", path); |
| return new DateTime.fromMillisecondsSinceEpoch(ms); |
| } |
| |
| Future setLastModified(DateTime time) { |
| int millis = time.millisecondsSinceEpoch; |
| return _dispatchWithNamespace(_IOService.fileSetLastModified, [ |
| null, |
| _rawPath, |
| millis, |
| ]).then((response) { |
| _checkForErrorResponse(response, "Cannot set modification time", path); |
| return null; |
| }); |
| } |
| |
| external static _setLastModified( |
| _Namespace namespace, |
| Uint8List rawPath, |
| int millis, |
| ); |
| |
| void setLastModifiedSync(DateTime time) { |
| int millis = time.millisecondsSinceEpoch; |
| var result = _setLastModified(_Namespace._namespace, _rawPath, millis); |
| if (result is OSError) { |
| throw new FileSystemException( |
| "Failed to set file modification time", |
| path, |
| result, |
| ); |
| } |
| } |
| |
| external static _open(_Namespace namespace, Uint8List rawPath, int mode); |
| |
| RandomAccessFile openSync({FileMode mode = FileMode.read}) { |
| if (mode != FileMode.read && |
| mode != FileMode.write && |
| mode != FileMode.append && |
| mode != FileMode.writeOnly && |
| mode != FileMode.writeOnlyAppend) { |
| throw new ArgumentError('Invalid file mode for this operation'); |
| } |
| var id = _open(_Namespace._namespace, _rawPath, mode._mode); |
| throwIfError(id, "Cannot open file", path); |
| return new _RandomAccessFile(id, _path); |
| } |
| |
| external static int _openStdio(int fd); |
| |
| static RandomAccessFile _openStdioSync(int fd) { |
| var id = _openStdio(fd); |
| if (id == 0) { |
| throw new FileSystemException("Cannot open stdio file for: $fd"); |
| } |
| return new _RandomAccessFile(id, ""); |
| } |
| |
| Stream<List<int>> openRead([int? start, int? end]) { |
| return new _FileStream(path, start, end); |
| } |
| |
| IOSink openWrite({FileMode mode = FileMode.write, Encoding encoding = utf8}) { |
| if (mode != FileMode.write && |
| mode != FileMode.append && |
| mode != FileMode.writeOnly && |
| mode != FileMode.writeOnlyAppend) { |
| throw new ArgumentError('Invalid file mode for this operation'); |
| } |
| var consumer = new _FileStreamConsumer(this, mode); |
| return new IOSink(consumer, encoding: encoding); |
| } |
| |
| Future<Uint8List> readAsBytes() { |
| Future<Uint8List> readUnsized(RandomAccessFile file) { |
| var builder = new BytesBuilder(copy: false); |
| var completer = new Completer<Uint8List>(); |
| void read() { |
| file.read(_blockSize).then((data) { |
| if (data.length > 0) { |
| builder.add(data); |
| read(); |
| } else { |
| completer.complete(builder.takeBytes()); |
| } |
| }, onError: completer.completeError); |
| } |
| |
| read(); |
| return completer.future; |
| } |
| |
| Future<Uint8List> readSized(RandomAccessFile file, int length) { |
| var data = Uint8List(length); |
| var offset = 0; |
| var completer = new Completer<Uint8List>(); |
| void read() { |
| file.readInto(data, offset, min(offset + _maxReadSize, length)).then(( |
| readSize, |
| ) { |
| if (readSize > 0) { |
| offset += readSize; |
| read(); |
| } else { |
| assert(readSize == 0); |
| if (offset < length) { |
| data = Uint8List.sublistView(data, 0, offset); |
| } |
| completer.complete(data); |
| } |
| }, onError: completer.completeError); |
| } |
| |
| read(); |
| return completer.future; |
| } |
| |
| return open().then((file) { |
| return file |
| .length() |
| .then((length) { |
| if (length == 0) { |
| // May be character device, try to read it in chunks. |
| return readUnsized(file); |
| } |
| return readSized(file, length); |
| }) |
| .whenComplete(file.close); |
| }); |
| } |
| |
| Uint8List readAsBytesSync() { |
| var opened = openSync(); |
| try { |
| Uint8List data; |
| var length = opened.lengthSync(); |
| if (length == 0) { |
| // May be character device, try to read it in chunks. |
| var builder = new BytesBuilder(copy: false); |
| do { |
| data = opened.readSync(_blockSize); |
| if (data.length > 0) { |
| builder.add(data); |
| } |
| } while (data.length > 0); |
| data = builder.takeBytes(); |
| } else { |
| data = Uint8List(length); |
| var offset = 0; |
| |
| while (offset < length) { |
| final readSize = opened.readIntoSync( |
| data, |
| offset, |
| min(offset + _maxReadSize, length), |
| ); |
| if (readSize == 0) { |
| break; |
| } |
| offset += readSize; |
| } |
| |
| if (offset < length) { |
| data = Uint8List.view(data.buffer, 0, offset); |
| } |
| } |
| return data; |
| } finally { |
| opened.closeSync(); |
| } |
| } |
| |
| String _tryDecode(List<int> bytes, Encoding encoding) { |
| try { |
| return encoding.decode(bytes); |
| } catch (_) { |
| throw new FileSystemException( |
| "Failed to decode data using encoding '${encoding.name}'", |
| path, |
| ); |
| } |
| } |
| |
| Future<String> readAsString({Encoding encoding = utf8}) async => |
| _tryDecode(await readAsBytes(), encoding); |
| |
| String readAsStringSync({Encoding encoding = utf8}) => |
| _tryDecode(readAsBytesSync(), encoding); |
| |
| Future<List<String>> readAsLines({Encoding encoding = utf8}) => |
| readAsString(encoding: encoding).then(const LineSplitter().convert); |
| |
| List<String> readAsLinesSync({Encoding encoding = utf8}) => |
| const LineSplitter().convert(readAsStringSync(encoding: encoding)); |
| |
| Future<File> writeAsBytes( |
| List<int> bytes, { |
| FileMode mode = FileMode.write, |
| bool flush = false, |
| }) { |
| return open(mode: mode).then((file) { |
| return file |
| .writeFrom(bytes, 0, bytes.length) |
| .then<File>((_) { |
| if (flush) return file.flush().then((_) => this); |
| return this; |
| }) |
| .whenComplete(file.close); |
| }); |
| } |
| |
| void writeAsBytesSync( |
| List<int> bytes, { |
| FileMode mode = FileMode.write, |
| bool flush = false, |
| }) { |
| RandomAccessFile opened = openSync(mode: mode); |
| try { |
| opened.writeFromSync(bytes, 0, bytes.length); |
| if (flush) opened.flushSync(); |
| } finally { |
| opened.closeSync(); |
| } |
| } |
| |
| Future<File> writeAsString( |
| String contents, { |
| FileMode mode = FileMode.write, |
| Encoding encoding = utf8, |
| bool flush = false, |
| }) { |
| try { |
| return writeAsBytes(encoding.encode(contents), mode: mode, flush: flush); |
| } catch (e) { |
| return new Future.error(e); |
| } |
| } |
| |
| void writeAsStringSync( |
| String contents, { |
| FileMode mode = FileMode.write, |
| Encoding encoding = utf8, |
| bool flush = false, |
| }) { |
| writeAsBytesSync(encoding.encode(contents), mode: mode, flush: flush); |
| } |
| |
| String toString() => "File: '$path'"; |
| |
| static throwIfError(Object result, String msg, String path) { |
| if (result is OSError) { |
| throw FileSystemException._fromOSError(result, msg, 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 _RandomAccessFileOps { |
| external factory _RandomAccessFileOps._(int pointer); |
| |
| int _getPointer(); |
| int get fd; |
| int close(); |
| readByte(); |
| read(int bytes); |
| readInto(List<int> buffer, int start, int? end); |
| writeByte(int value); |
| writeFrom(List<int> buffer, int start, int? end); |
| position(); |
| setPosition(int position); |
| truncate(int length); |
| length(); |
| flush(); |
| lock(int lock, int start, int end); |
| } |
| |
| @pragma("vm:entry-point") |
| class _RandomAccessFile implements RandomAccessFile { |
| static bool _connectedResourceHandler = false; |
| |
| final String path; |
| |
| bool _asyncDispatched = false; |
| |
| late _FileResourceInfo _resourceInfo; |
| _RandomAccessFileOps _ops; |
| |
| @pragma("vm:entry-point") |
| _RandomAccessFile(int pointer, this.path) |
| : _ops = new _RandomAccessFileOps._(pointer) { |
| _resourceInfo = new _FileResourceInfo(this); |
| _maybeConnectHandler(); |
| } |
| |
| void _maybePerformCleanup() { |
| if (closed) { |
| _FileResourceInfo.fileClosed(_resourceInfo); |
| } |
| } |
| |
| _maybeConnectHandler() { |
| if (!const bool.fromEnvironment("dart.vm.product") && |
| !_connectedResourceHandler) { |
| // TODO(ricow): We probably need to set these in some initialization code. |
| // We need to make sure that these are always available from the |
| // observatory even if no files (or sockets for the socket ones) are |
| // open. |
| registerExtension( |
| 'ext.dart.io.getOpenFiles', |
| _FileResourceInfo.getOpenFiles, |
| ); |
| registerExtension( |
| 'ext.dart.io.getOpenFileById', |
| _FileResourceInfo.getOpenFileInfoMapByID, |
| ); |
| _connectedResourceHandler = true; |
| } |
| } |
| |
| Future<void> close() { |
| return _dispatch(_IOService.fileClose, [null], markClosed: true).then(( |
| result, |
| ) { |
| if (result == -1) { |
| throw new FileSystemException("Cannot close file", path); |
| } |
| closed = closed || (result == 0); |
| _maybePerformCleanup(); |
| }); |
| } |
| |
| void closeSync() { |
| _checkAvailable(); |
| var id = _ops.close(); |
| if (id == -1) { |
| throw new FileSystemException("Cannot close file", path); |
| } |
| closed = closed || (id == 0); |
| _maybePerformCleanup(); |
| } |
| |
| Future<int> readByte() { |
| return _dispatch(_IOService.fileReadByte, [null]).then((response) { |
| _checkForErrorResponse(response, "readByte failed", path); |
| _resourceInfo.addRead(1); |
| return response as int; |
| }); |
| } |
| |
| int readByteSync() { |
| _checkAvailable(); |
| var result = _ops.readByte(); |
| if (result is OSError) { |
| throw new FileSystemException("readByte failed", path, result); |
| } |
| _resourceInfo.addRead(1); |
| return result; |
| } |
| |
| Future<Uint8List> read(int bytes) { |
| // TODO(40614): Remove once non-nullability is sound. |
| ArgumentError.checkNotNull(bytes, "bytes"); |
| return _dispatch(_IOService.fileRead, [null, bytes]).then((response) { |
| _checkForErrorResponse(response, "read failed", path); |
| var result = (response as List<Object?>)[1] as Uint8List; |
| _resourceInfo.addRead(result.length); |
| return result; |
| }); |
| } |
| |
| Uint8List readSync(int bytes) { |
| // TODO(40614): Remove once non-nullability is sound. |
| ArgumentError.checkNotNull(bytes, "bytes"); |
| _checkAvailable(); |
| var result = _ops.read(bytes); |
| if (result is! Uint8List) { |
| throw new FileSystemException("readSync failed", path, result as OSError); |
| } |
| _resourceInfo.addRead(result.length); |
| return result; |
| } |
| |
| Future<int> readInto(List<int> buffer, [int start = 0, int? end]) { |
| // TODO(40614): Remove once non-nullability is sound. |
| ArgumentError.checkNotNull(buffer, "buffer"); |
| end = RangeError.checkValidRange(start, end, buffer.length); |
| if (end == start) { |
| return new Future.value(0); |
| } |
| int length = end - start; |
| return _dispatch(_IOService.fileReadInto, [null, length]).then((response) { |
| _checkForErrorResponse(response, "readInto failed", path); |
| var responseList = response as List<Object?>; |
| var read = responseList[1] as int; |
| var data = responseList[2] as List<int>; |
| buffer.setRange(start, start + read, data); |
| _resourceInfo.addRead(read); |
| return read; |
| }); |
| } |
| |
| int readIntoSync(List<int> buffer, [int start = 0, int? end]) { |
| // TODO(40614): Remove once non-nullability is sound. |
| ArgumentError.checkNotNull(buffer, "buffer"); |
| _checkAvailable(); |
| end = RangeError.checkValidRange(start, end, buffer.length); |
| if (end == start) { |
| return 0; |
| } |
| var result = _ops.readInto(buffer, start, end); |
| if (result is OSError) { |
| throw new FileSystemException("readInto failed", path, result); |
| } |
| _resourceInfo.addRead(result); |
| return result; |
| } |
| |
| Future<RandomAccessFile> writeByte(int value) { |
| // TODO(40614): Remove once non-nullability is sound. |
| ArgumentError.checkNotNull(value, "value"); |
| return _dispatch(_IOService.fileWriteByte, [null, value]).then((response) { |
| _checkForErrorResponse(response, "writeByte failed", path); |
| _resourceInfo.addWrite(1); |
| return this; |
| }); |
| } |
| |
| int writeByteSync(int value) { |
| _checkAvailable(); |
| // TODO(40614): Remove once non-nullability is sound. |
| ArgumentError.checkNotNull(value, "value"); |
| var result = _ops.writeByte(value); |
| if (result is OSError) { |
| throw new FileSystemException("writeByte failed", path, result); |
| } |
| _resourceInfo.addWrite(1); |
| return result; |
| } |
| |
| Future<RandomAccessFile> writeFrom( |
| List<int> buffer, [ |
| int start = 0, |
| int? end, |
| ]) { |
| // TODO(40614): Remove once non-nullability is sound. |
| ArgumentError.checkNotNull(buffer, "buffer"); |
| ArgumentError.checkNotNull(start, "start"); |
| end = RangeError.checkValidRange(start, end, buffer.length); |
| if (end == start) { |
| return new Future.value(this); |
| } |
| _BufferAndStart result; |
| try { |
| result = _ensureFastAndSerializableByteData(buffer, start, end); |
| } catch (e) { |
| return new Future.error(e); |
| } |
| |
| List request = new List<dynamic>.filled(4, null); |
| request[0] = null; |
| request[1] = result.buffer; |
| request[2] = result.start; |
| request[3] = end - (start - result.start); |
| return _dispatch(_IOService.fileWriteFrom, request).then((response) { |
| _checkForErrorResponse(response, "writeFrom failed", path); |
| _resourceInfo.addWrite(end! - (start - result.start)); |
| return this; |
| }); |
| } |
| |
| void writeFromSync(List<int> buffer, [int start = 0, int? end]) { |
| _checkAvailable(); |
| // TODO(40614): Remove once non-nullability is sound. |
| ArgumentError.checkNotNull(buffer, "buffer"); |
| ArgumentError.checkNotNull(start, "start"); |
| end = RangeError.checkValidRange(start, end, buffer.length); |
| if (end == start) { |
| return; |
| } |
| _BufferAndStart bufferAndStart = _ensureFastAndSerializableByteData( |
| buffer, |
| start, |
| end, |
| ); |
| var result = _ops.writeFrom( |
| bufferAndStart.buffer, |
| bufferAndStart.start, |
| end - (start - bufferAndStart.start), |
| ); |
| if (result is OSError) { |
| throw new FileSystemException("writeFrom failed", path, result); |
| } |
| _resourceInfo.addWrite(end - (start - bufferAndStart.start)); |
| } |
| |
| Future<RandomAccessFile> writeString( |
| String string, { |
| Encoding encoding = utf8, |
| }) { |
| // TODO(40614): Remove once non-nullability is sound. |
| ArgumentError.checkNotNull(encoding, "encoding"); |
| var data = encoding.encode(string); |
| return writeFrom(data, 0, data.length); |
| } |
| |
| void writeStringSync(String string, {Encoding encoding = utf8}) { |
| // TODO(40614): Remove once non-nullability is sound. |
| ArgumentError.checkNotNull(encoding, "encoding"); |
| var data = encoding.encode(string); |
| writeFromSync(data, 0, data.length); |
| } |
| |
| Future<int> position() { |
| return _dispatch(_IOService.filePosition, [null]).then((response) { |
| _checkForErrorResponse(response, "position failed", path); |
| return response as int; |
| }); |
| } |
| |
| int positionSync() { |
| _checkAvailable(); |
| var result = _ops.position(); |
| if (result is OSError) { |
| throw new FileSystemException("position failed", path, result); |
| } |
| return result; |
| } |
| |
| Future<RandomAccessFile> setPosition(int position) { |
| return _dispatch(_IOService.fileSetPosition, [null, position]).then(( |
| response, |
| ) { |
| _checkForErrorResponse(response, "setPosition failed", path); |
| return this; |
| }); |
| } |
| |
| void setPositionSync(int position) { |
| _checkAvailable(); |
| var result = _ops.setPosition(position); |
| if (result is OSError) { |
| throw new FileSystemException("setPosition failed", path, result); |
| } |
| } |
| |
| Future<RandomAccessFile> truncate(int length) { |
| return _dispatch(_IOService.fileTruncate, [null, length]).then((response) { |
| _checkForErrorResponse(response, "truncate failed", path); |
| return this; |
| }); |
| } |
| |
| void truncateSync(int length) { |
| _checkAvailable(); |
| var result = _ops.truncate(length); |
| if (result is OSError) { |
| throw new FileSystemException("truncate failed", path, result); |
| } |
| } |
| |
| Future<int> length() { |
| return _dispatch(_IOService.fileLength, [null]).then((response) { |
| _checkForErrorResponse(response, "length failed", path); |
| return response as int; |
| }); |
| } |
| |
| int lengthSync() { |
| _checkAvailable(); |
| var result = _ops.length(); |
| if (result is OSError) { |
| throw new FileSystemException("length failed", path, result); |
| } |
| return result; |
| } |
| |
| Future<RandomAccessFile> flush() { |
| return _dispatch(_IOService.fileFlush, [null]).then((response) { |
| _checkForErrorResponse(response, "flush failed", path); |
| return this; |
| }); |
| } |
| |
| void flushSync() { |
| _checkAvailable(); |
| var result = _ops.flush(); |
| if (result is OSError) { |
| throw new FileSystemException("flush failed", path, result); |
| } |
| } |
| |
| static const int lockUnlock = 0; |
| // static const int lockShared = 1; |
| // static const int lockExclusive = 2; |
| // static const int lockBlockingShared = 3; |
| // static const int lockBlockingExclusive = 4; |
| |
| int _fileLockValue(FileLock fl) => fl._type; |
| |
| Future<RandomAccessFile> lock([ |
| FileLock mode = FileLock.exclusive, |
| int start = 0, |
| int end = -1, |
| ]) { |
| // TODO(40614): Remove once non-nullability is sound. |
| ArgumentError.checkNotNull(mode, "mode"); |
| ArgumentError.checkNotNull(start, "start"); |
| ArgumentError.checkNotNull(end, "end"); |
| if ((start < 0) || (end < -1) || ((end != -1) && (start >= end))) { |
| throw new ArgumentError(); |
| } |
| int lock = _fileLockValue(mode); |
| return _dispatch(_IOService.fileLock, [null, lock, start, end]).then(( |
| response, |
| ) { |
| _checkForErrorResponse(response, 'lock failed', path); |
| return this; |
| }); |
| } |
| |
| Future<RandomAccessFile> unlock([int start = 0, int end = -1]) { |
| // TODO(40614): Remove once non-nullability is sound. |
| ArgumentError.checkNotNull(start, "start"); |
| ArgumentError.checkNotNull(end, "end"); |
| if (start == end) { |
| throw new ArgumentError(); |
| } |
| return _dispatch(_IOService.fileLock, [null, lockUnlock, start, end]).then(( |
| response, |
| ) { |
| _checkForErrorResponse(response, 'unlock failed', path); |
| return this; |
| }); |
| } |
| |
| void lockSync([ |
| FileLock mode = FileLock.exclusive, |
| int start = 0, |
| int end = -1, |
| ]) { |
| _checkAvailable(); |
| // TODO(40614): Remove once non-nullability is sound. |
| ArgumentError.checkNotNull(mode, "mode"); |
| ArgumentError.checkNotNull(start, "start"); |
| ArgumentError.checkNotNull(end, "end"); |
| if ((start < 0) || (end < -1) || ((end != -1) && (start >= end))) { |
| throw new ArgumentError(); |
| } |
| int lock = _fileLockValue(mode); |
| var result = _ops.lock(lock, start, end); |
| if (result is OSError) { |
| throw new FileSystemException('lock failed', path, result); |
| } |
| } |
| |
| void unlockSync([int start = 0, int end = -1]) { |
| _checkAvailable(); |
| // TODO(40614): Remove once non-nullability is sound. |
| ArgumentError.checkNotNull(start, "start"); |
| ArgumentError.checkNotNull(end, "end"); |
| if (start == end) { |
| throw new ArgumentError(); |
| } |
| var result = _ops.lock(lockUnlock, start, end); |
| if (result is OSError) { |
| throw new FileSystemException('unlock failed', path, result); |
| } |
| } |
| |
| bool closed = false; |
| |
| int get fd => _ops.fd; |
| |
| // WARNING: |
| // Calling this function will increase the reference count on the native |
| // object that implements the file 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() => _ops._getPointer(); |
| |
| Future<Object?> _dispatch(int request, List data, {bool markClosed = false}) { |
| if (closed) { |
| return new Future.error(new FileSystemException("File closed", path)); |
| } |
| if (_asyncDispatched) { |
| var msg = "An async operation is currently pending"; |
| return new Future.error(new FileSystemException(msg, path)); |
| } |
| if (markClosed) { |
| // Set closed to true to ensure that no more async requests can be issued |
| // for this file. |
| closed = true; |
| } |
| _asyncDispatched = true; |
| data[0] = _pointer(); |
| return _IOService._dispatch(request, data).whenComplete(() { |
| _asyncDispatched = false; |
| }); |
| } |
| |
| void _checkAvailable() { |
| if (_asyncDispatched) { |
| throw new FileSystemException( |
| "An async operation is currently pending", |
| path, |
| ); |
| } |
| if (closed) { |
| throw new FileSystemException("File closed", path); |
| } |
| } |
| } |
| |
| class _ReadPipe extends _FileStream implements ReadPipe { |
| _ReadPipe(RandomAccessFile file) : super.forRandomAccessFile(file); |
| } |
| |
| class _WritePipe extends _IOSinkImpl implements WritePipe { |
| RandomAccessFile _file; |
| _WritePipe(file) |
| : this._file = file, |
| super(_FileStreamConsumer.fromRandomAccessFile(file), utf8); |
| } |
| |
| class _Pipe implements Pipe { |
| final ReadPipe _readPipe; |
| final WritePipe _writePipe; |
| |
| ReadPipe get read => _readPipe; |
| WritePipe get write => _writePipe; |
| |
| _Pipe(this._readPipe, this._writePipe); |
| |
| static Future<_Pipe> create() { |
| final completer = Completer<_Pipe>.sync(); |
| |
| _File._dispatchWithNamespace(_IOService.fileCreatePipe, [null]).then(( |
| response, |
| ) { |
| final filePointers = (response as List).cast<int>(); |
| completer.complete( |
| _Pipe( |
| _ReadPipe(_RandomAccessFile(filePointers[0], '')), |
| _WritePipe(_RandomAccessFile(filePointers[1], '')), |
| ), |
| ); |
| }); |
| return completer.future; |
| } |
| |
| factory _Pipe.createSync() { |
| final filePointers = _File._createPipe(_Namespace._namespace); |
| return _Pipe( |
| _ReadPipe(_RandomAccessFile(filePointers[0] as int, '')), |
| _WritePipe(_RandomAccessFile(filePointers[1] as int, '')), |
| ); |
| } |
| } |