mirror of
https://github.com/dart-lang/sdk
synced 2024-10-07 00:20:17 +00:00
54faa31964
dart2js.aot.x64 20942544 -> 20852448 (-90k) TEST=ci Bug: https://github.com/dart-lang/sdk/issues/45469 CoreLibraryReviewExempt: VM-only Change-Id: I5ee6a4019af1fa4a0815ac05a42bb4883d74d8a3 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/323503 Reviewed-by: Ben Konyi <bkonyi@google.com> Commit-Queue: Ryan Macnak <rmacnak@google.com>
1154 lines
35 KiB
Dart
1154 lines
35 KiB
Dart
// 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, '')));
|
|
}
|
|
}
|