f97265d731
The _FileUtils class is not needed it is used to hold methods used from multiple places. After we started using native ports for the async API most of these are used from only one place. BUG= R=sgjesse@google.com Review URL: https://codereview.chromium.org//11023003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@13054 260f80e4-7a28-3924-810f-c04153c831b5
1096 lines
32 KiB
Dart
1096 lines
32 KiB
Dart
// 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.
|
|
|
|
class _FileInputStream extends _BaseDataInputStream implements InputStream {
|
|
_FileInputStream(String name)
|
|
: _data = const [],
|
|
_position = 0,
|
|
_filePosition = 0 {
|
|
var file = new File(name);
|
|
var future = file.open(FileMode.READ);
|
|
future.handleException((e) {
|
|
_reportError(e);
|
|
return true;
|
|
});
|
|
future.then(_setupOpenedFile);
|
|
}
|
|
|
|
_FileInputStream.fromStdio(int fd)
|
|
: _data = const [],
|
|
_position = 0,
|
|
_filePosition = 0 {
|
|
assert(fd == 0);
|
|
_setupOpenedFile(_File._openStdioSync(fd));
|
|
}
|
|
|
|
void _setupOpenedFile(RandomAccessFile openedFile) {
|
|
_openedFile = openedFile;
|
|
if (_streamMarkedClosed) {
|
|
// This input stream has already been closed.
|
|
_fileLength = 0;
|
|
_closeFile();
|
|
return;
|
|
}
|
|
var futureOpen = _openedFile.length();
|
|
futureOpen.then((len) {
|
|
_fileLength = len;
|
|
_fillBuffer();
|
|
});
|
|
futureOpen.handleException((e) {
|
|
_reportError(e);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
void _closeFile() {
|
|
if (_openedFile == null) {
|
|
_streamMarkedClosed = true;
|
|
return;
|
|
}
|
|
if (available() == 0) _cancelScheduledDataCallback();
|
|
if (!_openedFile.closed) {
|
|
_openedFile.close().then((ignore) {
|
|
_streamMarkedClosed = true;
|
|
_checkScheduleCallbacks();
|
|
});
|
|
}
|
|
}
|
|
|
|
void _fillBuffer() {
|
|
Expect.equals(_position, _data.length);
|
|
if (_openedFile == null) return; // Called before the file is opened.
|
|
int size = min(_bufferLength, _fileLength - _filePosition);
|
|
if (size == 0) {
|
|
_closeFile();
|
|
return;
|
|
}
|
|
// If there is currently a _fillBuffer call waiting on readList,
|
|
// let it fill the buffer instead of us.
|
|
if (_activeFillBufferCall) return;
|
|
_activeFillBufferCall = true;
|
|
if (_data.length != size) {
|
|
_data = new Uint8List(size);
|
|
// Maintain the invariant signalling that the buffer is empty.
|
|
_position = _data.length;
|
|
}
|
|
var future = _openedFile.readList(_data, 0, _data.length);
|
|
future.then((read) {
|
|
_filePosition += read;
|
|
if (read != _data.length) {
|
|
_data = _data.getRange(0, read);
|
|
}
|
|
_position = 0;
|
|
_activeFillBufferCall = false;
|
|
|
|
if (_fileLength == _filePosition) {
|
|
_closeFile();
|
|
}
|
|
_checkScheduleCallbacks();
|
|
});
|
|
future.handleException((e) {
|
|
_activeFillBufferCall = false;
|
|
_reportError(e);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
int available() {
|
|
return closed ? 0 : _data.length - _position;
|
|
}
|
|
|
|
void pipe(OutputStream output, [bool close = true]) {
|
|
_pipe(this, output, close: close);
|
|
}
|
|
|
|
void _finishRead() {
|
|
if (_position == _data.length && !_streamMarkedClosed) {
|
|
_fillBuffer();
|
|
} else {
|
|
_checkScheduleCallbacks();
|
|
}
|
|
}
|
|
|
|
List<int> _read(int bytesToRead) {
|
|
List<int> result;
|
|
if (_position == 0 && bytesToRead == _data.length) {
|
|
result = _data;
|
|
_data = const [];
|
|
} else {
|
|
result = new Uint8List(bytesToRead);
|
|
result.setRange(0, bytesToRead, _data, _position);
|
|
_position += bytesToRead;
|
|
}
|
|
_finishRead();
|
|
return result;
|
|
}
|
|
|
|
int _readInto(List<int> buffer, int offset, int len) {
|
|
buffer.setRange(offset, len, _data, _position);
|
|
_position += len;
|
|
_finishRead();
|
|
return len;
|
|
}
|
|
|
|
void _close() {
|
|
_data = const [];
|
|
_position = 0;
|
|
_filePosition = 0;
|
|
_fileLength = 0;
|
|
_closeFile();
|
|
}
|
|
|
|
static const int _bufferLength = 64 * 1024;
|
|
|
|
RandomAccessFile _openedFile;
|
|
List<int> _data;
|
|
int _position;
|
|
int _filePosition;
|
|
int _fileLength;
|
|
bool _activeFillBufferCall = false;
|
|
}
|
|
|
|
|
|
class _PendingOperation {
|
|
const _PendingOperation(this._id);
|
|
static const _PendingOperation CLOSE = const _PendingOperation(0);
|
|
static const _PendingOperation FLUSH = const _PendingOperation(1);
|
|
final int _id;
|
|
}
|
|
|
|
|
|
class _FileOutputStream extends _BaseOutputStream implements OutputStream {
|
|
_FileOutputStream(String name, FileMode mode) {
|
|
_pendingOperations = new List();
|
|
var f = new File(name);
|
|
var openFuture = f.open(mode);
|
|
openFuture.then((openedFile) {
|
|
_file = openedFile;
|
|
_processPendingOperations();
|
|
});
|
|
openFuture.handleException((e) {
|
|
_reportError(e);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
_FileOutputStream.fromStdio(int fd) {
|
|
assert(1 <= fd && fd <= 2);
|
|
_file = _File._openStdioSync(fd);
|
|
}
|
|
|
|
bool write(List<int> buffer, [bool copyBuffer = false]) {
|
|
var data = buffer;
|
|
if (copyBuffer) {
|
|
var length = buffer.length;
|
|
data = new Uint8List(length);
|
|
data.setRange(0, length, buffer, 0);
|
|
}
|
|
if (_file == null) {
|
|
_pendingOperations.add(data);
|
|
} else {
|
|
_write(data, 0, data.length);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool writeFrom(List<int> buffer, [int offset = 0, int len]) {
|
|
// A copy is required by the interface.
|
|
var length = buffer.length - offset;
|
|
if (len != null) {
|
|
if (len > length) throw new IndexOutOfRangeException(len);
|
|
length = len;
|
|
}
|
|
var copy = new Uint8List(length);
|
|
copy.setRange(0, length, buffer, offset);
|
|
return write(copy);
|
|
}
|
|
|
|
|
|
void flush() {
|
|
if (_file == null) {
|
|
_pendingOperations.add(_PendingOperation.FLUSH);
|
|
} else {
|
|
_file.flush().then((ignored) => null);
|
|
}
|
|
}
|
|
|
|
|
|
void close() {
|
|
_streamMarkedClosed = true;
|
|
if (_file == null) {
|
|
_pendingOperations.add(_PendingOperation.CLOSE);
|
|
} else if (!_closeCallbackScheduled) {
|
|
_file.close().then((ignore) {
|
|
if (_onClosed != null) _onClosed();
|
|
});
|
|
_closeCallbackScheduled = true;
|
|
}
|
|
}
|
|
|
|
void set onNoPendingWrites(void callback()) {
|
|
_onNoPendingWrites = callback;
|
|
if ((_pendingOperations == null || _pendingOperations.length == 0) &&
|
|
outstandingWrites == 0 &&
|
|
!_streamMarkedClosed &&
|
|
_onNoPendingWrites != null) {
|
|
new Timer(0, (t) {
|
|
if (_onNoPendingWrites != null) {
|
|
_onNoPendingWrites();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void set onClosed(void callback()) {
|
|
_onClosed = callback;
|
|
}
|
|
|
|
void _processPendingOperations() {
|
|
_pendingOperations.forEach((buffer) {
|
|
if (buffer is _PendingOperation) {
|
|
if (buffer === _PendingOperation.CLOSE) {
|
|
close();
|
|
} else {
|
|
assert(buffer === _PendingOperation.FLUSH);
|
|
flush();
|
|
}
|
|
} else {
|
|
write(buffer);
|
|
}
|
|
});
|
|
_pendingOperations = null;
|
|
}
|
|
|
|
void _write(List<int> buffer, int offset, int len) {
|
|
outstandingWrites++;
|
|
var writeListFuture = _file.writeList(buffer, offset, len);
|
|
writeListFuture.then((ignore) {
|
|
outstandingWrites--;
|
|
if (outstandingWrites == 0 &&
|
|
!_streamMarkedClosed &&
|
|
_onNoPendingWrites != null) {
|
|
_onNoPendingWrites();
|
|
}
|
|
});
|
|
writeListFuture.handleException((e) {
|
|
outstandingWrites--;
|
|
_reportError(e);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
bool get closed => _streamMarkedClosed;
|
|
|
|
RandomAccessFile _file;
|
|
|
|
// When this is set to true the stream is marked closed. When a
|
|
// stream is marked closed no more data can be written.
|
|
bool _streamMarkedClosed = false;
|
|
|
|
// When this is set to true, the close callback has been scheduled and the
|
|
// stream will be fully closed once it's called.
|
|
bool _closeCallbackScheduled = false;
|
|
|
|
// Number of writes that have not yet completed.
|
|
int outstandingWrites = 0;
|
|
|
|
// List of pending writes that were issued before the underlying
|
|
// file was successfully opened.
|
|
List _pendingOperations;
|
|
|
|
Function _onNoPendingWrites;
|
|
Function _onClosed;
|
|
}
|
|
|
|
const int _EXISTS_REQUEST = 0;
|
|
const int _CREATE_REQUEST = 1;
|
|
const int _DELETE_REQUEST = 2;
|
|
const int _OPEN_REQUEST = 3;
|
|
const int _FULL_PATH_REQUEST = 4;
|
|
const int _DIRECTORY_REQUEST = 5;
|
|
const int _CLOSE_REQUEST = 6;
|
|
const int _POSITION_REQUEST = 7;
|
|
const int _SET_POSITION_REQUEST = 8;
|
|
const int _TRUNCATE_REQUEST = 9;
|
|
const int _LENGTH_REQUEST = 10;
|
|
const int _LENGTH_FROM_NAME_REQUEST = 11;
|
|
const int _LAST_MODIFIED_REQUEST = 12;
|
|
const int _FLUSH_REQUEST = 13;
|
|
const int _READ_BYTE_REQUEST = 14;
|
|
const int _WRITE_BYTE_REQUEST = 15;
|
|
const int _READ_LIST_REQUEST = 16;
|
|
const int _WRITE_LIST_REQUEST = 17;
|
|
const int _WRITE_STRING_REQUEST = 18;
|
|
|
|
// Base class for _File and _RandomAccessFile with shared functions.
|
|
class _FileBase {
|
|
bool _isErrorResponse(response) {
|
|
return response is List && response[0] != _SUCCESS_RESPONSE;
|
|
}
|
|
|
|
Exception _exceptionFromResponse(response, String message) {
|
|
assert(_isErrorResponse(response));
|
|
switch (response[_ERROR_RESPONSE_ERROR_TYPE]) {
|
|
case _ILLEGAL_ARGUMENT_RESPONSE:
|
|
return new ArgumentError();
|
|
case _OSERROR_RESPONSE:
|
|
var err = new OSError(response[_OSERROR_RESPONSE_MESSAGE],
|
|
response[_OSERROR_RESPONSE_ERROR_CODE]);
|
|
return new FileIOException(message, err);
|
|
case _FILE_CLOSED_RESPONSE:
|
|
return new FileIOException("File closed");
|
|
default:
|
|
return new Exception("Unknown error");
|
|
}
|
|
}
|
|
}
|
|
|
|
SendPort _newServicePort() native "File_NewServicePort";
|
|
|
|
// Class for encapsulating the native implementation of files.
|
|
class _File extends _FileBase implements File {
|
|
// Constructor for file.
|
|
_File(String this._name) {
|
|
if (_name is! String) {
|
|
throw new ArgumentError('${NoSuchMethodError.safeToString(_name)} '
|
|
'is not a String');
|
|
}
|
|
}
|
|
|
|
// Constructor from Path for file.
|
|
_File.fromPath(Path path) : this(path.toNativePath());
|
|
|
|
Future<bool> exists() {
|
|
_ensureFileService();
|
|
List request = new List(2);
|
|
request[0] = _EXISTS_REQUEST;
|
|
request[1] = _name;
|
|
return _fileService.call(request).transform((response) {
|
|
if (_isErrorResponse(response)) {
|
|
throw _exceptionFromResponse(response, "Cannot open file '$_name'");
|
|
}
|
|
return response;
|
|
});
|
|
}
|
|
|
|
|
|
static _exists(String name) native "File_Exists";
|
|
|
|
bool existsSync() {
|
|
var result = _exists(_name);
|
|
throwIfError(result, "Cannot check existence of file '$_name'");
|
|
return result;
|
|
}
|
|
|
|
Future<File> create() {
|
|
_ensureFileService();
|
|
List request = new List(2);
|
|
request[0] = _CREATE_REQUEST;
|
|
request[1] = _name;
|
|
return _fileService.call(request).transform((response) {
|
|
if (_isErrorResponse(response)) {
|
|
throw _exceptionFromResponse(response, "Cannot create file '$_name'");
|
|
}
|
|
return this;
|
|
});
|
|
}
|
|
|
|
static _create(String name) native "File_Create";
|
|
|
|
void createSync() {
|
|
var result = _create(_name);
|
|
throwIfError(result, "Cannot create file '$_name'");
|
|
}
|
|
|
|
Future<File> delete() {
|
|
_ensureFileService();
|
|
List request = new List(2);
|
|
request[0] = _DELETE_REQUEST;
|
|
request[1] = _name;
|
|
return _fileService.call(request).transform((response) {
|
|
if (_isErrorResponse(response)) {
|
|
throw _exceptionFromResponse(response, "Cannot delete file '$_name'");
|
|
}
|
|
return this;
|
|
});
|
|
}
|
|
|
|
static _delete(String name) native "File_Delete";
|
|
|
|
void deleteSync() {
|
|
var result = _delete(_name);
|
|
throwIfError(result, "Cannot delete file '$_name'");
|
|
}
|
|
|
|
Future<Directory> directory() {
|
|
_ensureFileService();
|
|
List request = new List(2);
|
|
request[0] = _DIRECTORY_REQUEST;
|
|
request[1] = _name;
|
|
return _fileService.call(request).transform((response) {
|
|
if (_isErrorResponse(response)) {
|
|
throw _exceptionFromResponse(response,
|
|
"Cannot retrieve directory for "
|
|
"file '$_name'");
|
|
}
|
|
return new Directory(response);
|
|
});
|
|
}
|
|
|
|
static _directory(String name) native "File_Directory";
|
|
|
|
Directory directorySync() {
|
|
var result = _directory(name);
|
|
throwIfError(result, "Cannot retrieve directory for file '$_name'");
|
|
return new Directory(result);
|
|
}
|
|
|
|
Future<RandomAccessFile> open([FileMode mode = FileMode.READ]) {
|
|
_ensureFileService();
|
|
Completer<RandomAccessFile> completer = new Completer<RandomAccessFile>();
|
|
if (mode != FileMode.READ &&
|
|
mode != FileMode.WRITE &&
|
|
mode != FileMode.APPEND) {
|
|
new Timer(0, (t) {
|
|
completer.completeException(new ArgumentError());
|
|
});
|
|
return completer.future;
|
|
}
|
|
List request = new List(3);
|
|
request[0] = _OPEN_REQUEST;
|
|
request[1] = _name;
|
|
request[2] = mode._mode; // Direct int value for serialization.
|
|
return _fileService.call(request).transform((response) {
|
|
if (_isErrorResponse(response)) {
|
|
throw _exceptionFromResponse(response, "Cannot open file '$_name'");
|
|
}
|
|
return new _RandomAccessFile(response, _name);
|
|
});
|
|
}
|
|
|
|
Future<int> length() {
|
|
_ensureFileService();
|
|
List request = new List(2);
|
|
request[0] = _LENGTH_FROM_NAME_REQUEST;
|
|
request[1] = _name;
|
|
return _fileService.call(request).transform((response) {
|
|
if (_isErrorResponse(response)) {
|
|
throw _exceptionFromResponse(response,
|
|
"Cannot retrieve length of "
|
|
"file '$_name'");
|
|
}
|
|
return response;
|
|
});
|
|
}
|
|
|
|
|
|
static _lengthFromName(String name) native "File_LengthFromName";
|
|
|
|
int lengthSync() {
|
|
var result = _lengthFromName(_name);
|
|
throwIfError(result, "Cannot retrieve length of file '$_name'");
|
|
return result;
|
|
}
|
|
|
|
Future<Date> lastModified() {
|
|
_ensureFileService();
|
|
List request = new List(2);
|
|
request[0] = _LAST_MODIFIED_REQUEST;
|
|
request[1] = _name;
|
|
return _fileService.call(request).transform((response) {
|
|
if (_isErrorResponse(response)) {
|
|
throw _exceptionFromResponse(response,
|
|
"Cannot retrieve modification time "
|
|
"for file '$_name'");
|
|
}
|
|
return new Date.fromMillisecondsSinceEpoch(response);
|
|
});
|
|
}
|
|
|
|
static _lastModified(String name) native "File_LastModified";
|
|
|
|
Date lastModifiedSync() {
|
|
var ms = _lastModified(name);
|
|
throwIfError(ms, "Cannot retrieve modification time for file '$_name'");
|
|
return new Date.fromMillisecondsSinceEpoch(ms);
|
|
}
|
|
|
|
static _open(String name, int mode) native "File_Open";
|
|
|
|
RandomAccessFile openSync([FileMode mode = FileMode.READ]) {
|
|
if (mode != FileMode.READ &&
|
|
mode != FileMode.WRITE &&
|
|
mode != FileMode.APPEND) {
|
|
throw new FileIOException("Unknown file mode. Use FileMode.READ, "
|
|
"FileMode.WRITE or FileMode.APPEND.");
|
|
}
|
|
var id = _open(_name, mode._mode);
|
|
throwIfError(id, "Cannot open file '$_name'");
|
|
return new _RandomAccessFile(id, _name);
|
|
}
|
|
|
|
static int _openStdio(int fd) native "File_OpenStdio";
|
|
|
|
static RandomAccessFile _openStdioSync(int fd) {
|
|
var id = _openStdio(fd);
|
|
if (id == 0) {
|
|
throw new FileIOException("Cannot open stdio file for: $fd");
|
|
}
|
|
return new _RandomAccessFile(id, "");
|
|
}
|
|
|
|
Future<String> fullPath() {
|
|
_ensureFileService();
|
|
List request = new List(2);
|
|
request[0] = _FULL_PATH_REQUEST;
|
|
request[1] = _name;
|
|
return _fileService.call(request).transform((response) {
|
|
if (_isErrorResponse(response)) {
|
|
throw _exceptionFromResponse(response,
|
|
"Cannot retrieve full path"
|
|
" for '$_name'");
|
|
}
|
|
return response;
|
|
});
|
|
}
|
|
|
|
static _fullPath(String name) native "File_FullPath";
|
|
|
|
String fullPathSync() {
|
|
var result = _fullPath(_name);
|
|
throwIfError(result, "Cannot retrieve full path for file '$_name'");
|
|
return result;
|
|
}
|
|
|
|
InputStream openInputStream() {
|
|
return new _FileInputStream(_name);
|
|
}
|
|
|
|
OutputStream openOutputStream([FileMode mode = FileMode.WRITE]) {
|
|
if (mode != FileMode.WRITE &&
|
|
mode != FileMode.APPEND) {
|
|
throw new FileIOException(
|
|
"Wrong FileMode. Use FileMode.WRITE or FileMode.APPEND");
|
|
}
|
|
return new _FileOutputStream(_name, mode);
|
|
}
|
|
|
|
Future<List<int>> readAsBytes() {
|
|
_ensureFileService();
|
|
Completer<List<int>> completer = new Completer<List<int>>();
|
|
var chunks = new _BufferList();
|
|
var stream = openInputStream();
|
|
stream.onClosed = () {
|
|
var result = chunks.readBytes(chunks.length);
|
|
if (result == null) result = <int>[];
|
|
completer.complete(result);
|
|
};
|
|
stream.onData = () {
|
|
var chunk = stream.read();
|
|
chunks.add(chunk);
|
|
};
|
|
stream.onError = completer.completeException;
|
|
return completer.future;
|
|
}
|
|
|
|
List<int> readAsBytesSync() {
|
|
var opened = openSync();
|
|
var length = opened.lengthSync();
|
|
var result = new Uint8List(length);
|
|
var read = opened.readListSync(result, 0, length);
|
|
if (read != length) {
|
|
throw new FileIOException("Failed to read file");
|
|
}
|
|
opened.closeSync();
|
|
return result;
|
|
}
|
|
|
|
Future<String> readAsText([Encoding encoding = Encoding.UTF_8]) {
|
|
_ensureFileService();
|
|
return readAsBytes().transform((bytes) {
|
|
if (bytes.length == 0) return "";
|
|
var decoder = _StringDecoders.decoder(encoding);
|
|
decoder.write(bytes);
|
|
return decoder.decoded();
|
|
});
|
|
}
|
|
|
|
String readAsTextSync([Encoding encoding = Encoding.UTF_8]) {
|
|
var decoder = _StringDecoders.decoder(encoding);
|
|
List<int> bytes = readAsBytesSync();
|
|
if (bytes.length == 0) return "";
|
|
decoder.write(bytes);
|
|
return decoder.decoded();
|
|
}
|
|
|
|
List<String> _getDecodedLines(_StringDecoder decoder) {
|
|
List<String> result = [];
|
|
var line = decoder.decodedLine;
|
|
while (line != null) {
|
|
result.add(line);
|
|
line = decoder.decodedLine;
|
|
}
|
|
// If there is more data with no terminating line break we treat
|
|
// it as the last line.
|
|
var data = decoder.decoded();
|
|
if (data != null) {
|
|
result.add(data);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Future<List<String>> readAsLines([Encoding encoding = Encoding.UTF_8]) {
|
|
_ensureFileService();
|
|
Completer<List<String>> completer = new Completer<List<String>>();
|
|
return readAsBytes().transform((bytes) {
|
|
var decoder = _StringDecoders.decoder(encoding);
|
|
decoder.write(bytes);
|
|
return _getDecodedLines(decoder);
|
|
});
|
|
}
|
|
|
|
List<String> readAsLinesSync([Encoding encoding = Encoding.UTF_8]) {
|
|
var decoder = _StringDecoders.decoder(encoding);
|
|
List<int> bytes = readAsBytesSync();
|
|
decoder.write(bytes);
|
|
return _getDecodedLines(decoder);
|
|
}
|
|
|
|
String get name => _name;
|
|
|
|
void _ensureFileService() {
|
|
if (_fileService == null) {
|
|
_fileService = _newServicePort();
|
|
}
|
|
}
|
|
|
|
static throwIfError(Object result, String msg) {
|
|
if (result is OSError) {
|
|
throw new FileIOException(msg, result);
|
|
}
|
|
}
|
|
|
|
final String _name;
|
|
|
|
SendPort _fileService;
|
|
}
|
|
|
|
|
|
class _RandomAccessFile extends _FileBase implements RandomAccessFile {
|
|
_RandomAccessFile(int this._id, String this._name);
|
|
|
|
Future<RandomAccessFile> close() {
|
|
Completer<RandomAccessFile> completer = new Completer<RandomAccessFile>();
|
|
if (closed) return _completeWithClosedException(completer);
|
|
_ensureFileService();
|
|
List request = new List(2);
|
|
request[0] = _CLOSE_REQUEST;
|
|
request[1] = _id;
|
|
// Set the id_ to 0 (NULL) to ensure the no more async requests
|
|
// can be issued for this file.
|
|
_id = 0;
|
|
return _fileService.call(request).transform((result) {
|
|
if (result != -1) {
|
|
_id = result;
|
|
return this;
|
|
} else {
|
|
throw new FileIOException("Cannot close file '$_name'");
|
|
}
|
|
});
|
|
}
|
|
|
|
static int _close(int id) native "File_Close";
|
|
|
|
void closeSync() {
|
|
_checkNotClosed();
|
|
var id = _close(_id);
|
|
if (id == -1) {
|
|
throw new FileIOException("Cannot close file '$_name'");
|
|
}
|
|
_id = id;
|
|
}
|
|
|
|
Future<int> readByte() {
|
|
_ensureFileService();
|
|
Completer<int> completer = new Completer<int>();
|
|
if (closed) return _completeWithClosedException(completer);
|
|
List request = new List(2);
|
|
request[0] = _READ_BYTE_REQUEST;
|
|
request[1] = _id;
|
|
return _fileService.call(request).transform((response) {
|
|
if (_isErrorResponse(response)) {
|
|
throw _exceptionFromResponse(response,
|
|
"readByte failed for file '$_name'");
|
|
}
|
|
return response;
|
|
});
|
|
}
|
|
|
|
static _readByte(int id) native "File_ReadByte";
|
|
|
|
int readByteSync() {
|
|
_checkNotClosed();
|
|
var result = _readByte(_id);
|
|
if (result is OSError) {
|
|
throw new FileIOException("readByte failed for file '$_name'", result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Future<int> readList(List<int> buffer, int offset, int bytes) {
|
|
_ensureFileService();
|
|
Completer<int> completer = new Completer<int>();
|
|
if (buffer is !List || offset is !int || bytes is !int) {
|
|
// Complete asynchronously so the user has a chance to setup
|
|
// handlers without getting exceptions when registering the
|
|
// then handler.
|
|
new Timer(0, (t) {
|
|
completer.completeException(new FileIOException(
|
|
"Invalid arguments to readList for file '$_name'"));
|
|
});
|
|
return completer.future;
|
|
};
|
|
if (closed) return _completeWithClosedException(completer);
|
|
List request = new List(3);
|
|
request[0] = _READ_LIST_REQUEST;
|
|
request[1] = _id;
|
|
request[2] = bytes;
|
|
return _fileService.call(request).transform((response) {
|
|
if (_isErrorResponse(response)) {
|
|
throw _exceptionFromResponse(response,
|
|
"readList failed for file '$_name'");
|
|
}
|
|
var read = response[1];
|
|
var data = response[2];
|
|
buffer.setRange(offset, read, data);
|
|
return read;
|
|
});
|
|
}
|
|
|
|
static void _checkReadWriteListArguments(int length, int offset, int bytes) {
|
|
if (offset < 0) throw new IndexOutOfRangeException(offset);
|
|
if (bytes < 0) throw new IndexOutOfRangeException(bytes);
|
|
if ((offset + bytes) > length) {
|
|
throw new IndexOutOfRangeException(offset + bytes);
|
|
}
|
|
}
|
|
|
|
static _readList(int id, List<int> buffer, int offset, int bytes)
|
|
native "File_ReadList";
|
|
|
|
int readListSync(List<int> buffer, int offset, int bytes) {
|
|
_checkNotClosed();
|
|
if (buffer is !List || offset is !int || bytes is !int) {
|
|
throw new FileIOException(
|
|
"Invalid arguments to readList for file '$_name'");
|
|
}
|
|
if (bytes == 0) return 0;
|
|
_checkReadWriteListArguments(buffer.length, offset, bytes);
|
|
var result = _readList(_id, buffer, offset, bytes);
|
|
if (result is OSError) {
|
|
throw new FileIOException("readList failed for file '$_name'",
|
|
result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Future<RandomAccessFile> writeByte(int value) {
|
|
_ensureFileService();
|
|
Completer<RandomAccessFile> completer = new Completer<RandomAccessFile>();
|
|
if (value is !int) {
|
|
// Complete asynchronously so the user has a chance to setup
|
|
// handlers without getting exceptions when registering the
|
|
// then handler.
|
|
new Timer(0, (t) {
|
|
completer.completeException(new FileIOException(
|
|
"Invalid argument to writeByte for file '$_name'"));
|
|
});
|
|
return completer.future;
|
|
}
|
|
if (closed) return _completeWithClosedException(completer);
|
|
List request = new List(3);
|
|
request[0] = _WRITE_BYTE_REQUEST;
|
|
request[1] = _id;
|
|
request[2] = value;
|
|
return _fileService.call(request).transform((response) {
|
|
if (_isErrorResponse(response)) {
|
|
throw _exceptionFromResponse(response,
|
|
"writeByte failed for file '$_name'");
|
|
}
|
|
return this;
|
|
});
|
|
}
|
|
|
|
static _writeByte(int id, int value) native "File_WriteByte";
|
|
|
|
int writeByteSync(int value) {
|
|
_checkNotClosed();
|
|
if (value is !int) {
|
|
throw new FileIOException(
|
|
"Invalid argument to writeByte for file '$_name'");
|
|
}
|
|
var result = _writeByte(_id, value);
|
|
if (result is OSError) {
|
|
throw new FileIOException("writeByte failed for file '$_name'",
|
|
result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Future<RandomAccessFile> writeList(List<int> buffer, int offset, int bytes) {
|
|
_ensureFileService();
|
|
Completer<RandomAccessFile> completer = new Completer<RandomAccessFile>();
|
|
if (buffer is !List || offset is !int || bytes is !int) {
|
|
// Complete asynchronously so the user has a chance to setup
|
|
// handlers without getting exceptions when registering the
|
|
// then handler.
|
|
new Timer(0, (t) {
|
|
completer.completeException(new FileIOException(
|
|
"Invalid arguments to writeList for file '$_name'"));
|
|
});
|
|
return completer.future;
|
|
}
|
|
if (closed) return _completeWithClosedException(completer);
|
|
|
|
_BufferAndOffset result;
|
|
try {
|
|
result = _ensureFastAndSerializableBuffer(buffer, offset, bytes);
|
|
} catch (e) {
|
|
// Complete asynchronously so the user has a chance to setup
|
|
// handlers without getting exceptions when registering the
|
|
// then handler.
|
|
new Timer(0, (t) => completer.completeException(e));
|
|
return completer.future;
|
|
}
|
|
|
|
List request = new List(5);
|
|
request[0] = _WRITE_LIST_REQUEST;
|
|
request[1] = _id;
|
|
request[2] = result.buffer;
|
|
request[3] = result.offset;
|
|
request[4] = bytes;
|
|
return _fileService.call(request).transform((response) {
|
|
if (_isErrorResponse(response)) {
|
|
throw _exceptionFromResponse(response,
|
|
"writeList failed for file '$_name'");
|
|
}
|
|
return this;
|
|
});
|
|
}
|
|
|
|
static _writeList(int id, List<int> buffer, int offset, int bytes)
|
|
native "File_WriteList";
|
|
|
|
int writeListSync(List<int> buffer, int offset, int bytes) {
|
|
_checkNotClosed();
|
|
if (buffer is !List || offset is !int || bytes is !int) {
|
|
throw new FileIOException(
|
|
"Invalid arguments to writeList for file '$_name'");
|
|
}
|
|
if (bytes == 0) return 0;
|
|
_checkReadWriteListArguments(buffer.length, offset, bytes);
|
|
_BufferAndOffset bufferAndOffset =
|
|
_ensureFastAndSerializableBuffer(buffer, offset, bytes);
|
|
var result =
|
|
_writeList(_id, bufferAndOffset.buffer, bufferAndOffset.offset, bytes);
|
|
if (result is OSError) {
|
|
throw new FileIOException("writeList failed for file '$_name'", result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Future<RandomAccessFile> writeString(String string,
|
|
[Encoding encoding = Encoding.UTF_8]) {
|
|
_ensureFileService();
|
|
Completer<RandomAccessFile> completer = new Completer<RandomAccessFile>();
|
|
if (closed) return _completeWithClosedException(completer);
|
|
List request = new List(3);
|
|
request[0] = _WRITE_STRING_REQUEST;
|
|
request[1] = _id;
|
|
request[2] = string;
|
|
return _fileService.call(request).transform((response) {
|
|
if (_isErrorResponse(response)) {
|
|
throw _exceptionFromResponse(response,
|
|
"writeString failed for file '$_name'");
|
|
}
|
|
return this;
|
|
});
|
|
}
|
|
|
|
static _writeString(int id, String string) native "File_WriteString";
|
|
|
|
int writeStringSync(String string, [Encoding encoding = Encoding.UTF_8]) {
|
|
_checkNotClosed();
|
|
if (string is !String) throw new ArgumentError();
|
|
var result = _writeString(_id, string);
|
|
if (result is OSError) {
|
|
throw new FileIOException("writeString failed for file '$_name'");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Future<int> position() {
|
|
_ensureFileService();
|
|
Completer<int> completer = new Completer<int>();
|
|
if (closed) return _completeWithClosedException(completer);
|
|
List request = new List(2);
|
|
request[0] = _POSITION_REQUEST;
|
|
request[1] = _id;
|
|
return _fileService.call(request).transform((response) {
|
|
if (_isErrorResponse(response)) {
|
|
throw _exceptionFromResponse(response,
|
|
"position failed for file '$_name'");
|
|
}
|
|
return response;
|
|
});
|
|
}
|
|
|
|
static _position(int id) native "File_Position";
|
|
|
|
int positionSync() {
|
|
_checkNotClosed();
|
|
var result = _position(_id);
|
|
if (result is OSError) {
|
|
throw new FileIOException("position failed for file '$_name'", result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Future<RandomAccessFile> setPosition(int position) {
|
|
_ensureFileService();
|
|
Completer<RandomAccessFile> completer = new Completer<RandomAccessFile>();
|
|
if (closed) return _completeWithClosedException(completer);
|
|
List request = new List(3);
|
|
request[0] = _SET_POSITION_REQUEST;
|
|
request[1] = _id;
|
|
request[2] = position;
|
|
return _fileService.call(request).transform((response) {
|
|
if (_isErrorResponse(response)) {
|
|
throw _exceptionFromResponse(response,
|
|
"setPosition failed for file '$_name'");
|
|
}
|
|
return this;
|
|
});
|
|
}
|
|
|
|
static _setPosition(int id, int position) native "File_SetPosition";
|
|
|
|
void setPositionSync(int position) {
|
|
_checkNotClosed();
|
|
var result = _setPosition(_id, position);
|
|
if (result is OSError) {
|
|
throw new FileIOException("setPosition failed for file '$_name'", result);
|
|
}
|
|
}
|
|
|
|
Future<RandomAccessFile> truncate(int length) {
|
|
_ensureFileService();
|
|
Completer<RandomAccessFile> completer = new Completer<RandomAccessFile>();
|
|
if (closed) return _completeWithClosedException(completer);
|
|
List request = new List(3);
|
|
request[0] = _TRUNCATE_REQUEST;
|
|
request[1] = _id;
|
|
request[2] = length;
|
|
return _fileService.call(request).transform((response) {
|
|
if (_isErrorResponse(response)) {
|
|
throw _exceptionFromResponse(response,
|
|
"truncate failed for file '$_name'");
|
|
}
|
|
return this;
|
|
});
|
|
}
|
|
|
|
static _truncate(int id, int length) native "File_Truncate";
|
|
|
|
void truncateSync(int length) {
|
|
_checkNotClosed();
|
|
var result = _truncate(_id, length);
|
|
if (result is OSError) {
|
|
throw new FileIOException("truncate failed for file '$_name'", result);
|
|
}
|
|
}
|
|
|
|
Future<int> length() {
|
|
_ensureFileService();
|
|
Completer<int> completer = new Completer<int>();
|
|
if (closed) return _completeWithClosedException(completer);
|
|
List request = new List(2);
|
|
request[0] = _LENGTH_REQUEST;
|
|
request[1] = _id;
|
|
return _fileService.call(request).transform((response) {
|
|
if (_isErrorResponse(response)) {
|
|
throw _exceptionFromResponse(response,
|
|
"length failed for file '$_name'");
|
|
}
|
|
return response;
|
|
});
|
|
}
|
|
|
|
static _length(int id) native "File_Length";
|
|
|
|
int lengthSync() {
|
|
_checkNotClosed();
|
|
var result = _length(_id);
|
|
if (result is OSError) {
|
|
throw new FileIOException("length failed for file '$_name'", result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Future<RandomAccessFile> flush() {
|
|
_ensureFileService();
|
|
Completer<RandomAccessFile> completer = new Completer<RandomAccessFile>();
|
|
if (closed) return _completeWithClosedException(completer);
|
|
List request = new List(2);
|
|
request[0] = _FLUSH_REQUEST;
|
|
request[1] = _id;
|
|
return _fileService.call(request).transform((response) {
|
|
if (_isErrorResponse(response)) {
|
|
throw _exceptionFromResponse(response,
|
|
"flush failed for file '$_name'");
|
|
}
|
|
return this;
|
|
});
|
|
}
|
|
|
|
static _flush(int id) native "File_Flush";
|
|
|
|
void flushSync() {
|
|
_checkNotClosed();
|
|
var result = _flush(_id);
|
|
if (result is OSError) {
|
|
throw new FileIOException("flush failed for file '$_name'", result);
|
|
}
|
|
}
|
|
|
|
String get name => _name;
|
|
|
|
void _ensureFileService() {
|
|
if (_fileService == null) {
|
|
_fileService = _newServicePort();
|
|
}
|
|
}
|
|
|
|
bool get closed => _id == 0;
|
|
|
|
void _checkNotClosed() {
|
|
if (closed) {
|
|
throw new FileIOException("File closed '$_name'");
|
|
}
|
|
}
|
|
|
|
Future _completeWithClosedException(Completer completer) {
|
|
new Timer(0, (t) {
|
|
completer.completeException(
|
|
new FileIOException("File closed '$_name'"));
|
|
});
|
|
return completer.future;
|
|
}
|
|
|
|
final String _name;
|
|
int _id;
|
|
|
|
SendPort _fileService;
|
|
}
|