| Index: runtime/bin/file_impl.dart
|
| diff --git a/runtime/bin/file_impl.dart b/runtime/bin/file_impl.dart
|
| index 5e9d877834c0177de4f18b02c99febc4e2f695db..abf87c82d49c2132967195387099604f01d2d83a 100644
|
| --- a/runtime/bin/file_impl.dart
|
| +++ b/runtime/bin/file_impl.dart
|
| @@ -114,187 +114,559 @@ class _FileOutputStream implements FileOutputStream {
|
| }
|
|
|
|
|
| +class _FileOperationIsolate extends Isolate {
|
| + static int EXISTS = 0;
|
| + static int OPEN = 1;
|
| + static int CLOSE = 2;
|
| + static int READ_BYTE = 3;
|
| + static int READ_LIST = 4;
|
| + static int WRITE_BYTE = 5;
|
| + static int WRITE_LIST = 6;
|
| + static int WRITE_STRING = 7;
|
| + static int POSITION = 8;
|
| + static int LENGTH = 9;
|
| + static int FLUSH = 10;
|
| + static int EXIT = 11;
|
| +
|
| + _FileOperationIsolate() : super.heavy();
|
| +
|
| + void handleOperation(Map message, SendPort ignored) {
|
| + switch (message["type"]) {
|
| + case EXISTS:
|
| + message["reply"].send(_File._exists(message["name"]),
|
| + port.toSendPort());
|
| + break;
|
| + case OPEN:
|
| + var name = message["name"];
|
| + var writable = message["writable"];
|
| + message["reply"].send(_File._open(name, writable),
|
| + port.toSendPort());
|
| + break;
|
| + case CLOSE:
|
| + message["reply"].send(_File._close(message["id"]),
|
| + port.toSendPort());
|
| + break;
|
| + case READ_BYTE:
|
| + message["reply"].send(_File._readByte(message["id"]),
|
| + port.toSendPort());
|
| + break;
|
| + case READ_LIST:
|
| + var replyPort = message["reply"];
|
| + var bytes = message["bytes"];
|
| + var offset = message["offset"];
|
| + var length = message["length"];
|
| + var id = message["id"];
|
| + if (bytes == 0) {
|
| + replyPort.send(0, port.toSendPort());
|
| + return;
|
| + }
|
| + int index = _File._checkReadWriteListArguments(length, offset, bytes);
|
| + if (index != 0) {
|
| + replyPort.send("index out of range in readList: $index",
|
| + port.toSendPort());
|
| + return;
|
| + }
|
| + var buffer = new List(bytes);
|
| + var result = { "read": _File._readList(id, buffer, 0, bytes),
|
| + "buffer": buffer };
|
| + replyPort.send(result, port.toSendPort());
|
| + break;
|
| + case WRITE_BYTE:
|
| + message["reply"].send(_File._writeByte(message["id"], message["value"]),
|
| + port.toSendPort());
|
| + break;
|
| + case WRITE_LIST:
|
| + var replyPort = message["reply"];
|
| + var buffer = message["buffer"];
|
| + var bytes = message["bytes"];
|
| + var offset = message["offset"];
|
| + var id = message["id"];
|
| + if (bytes == 0) {
|
| + replyPort.send(0, port.toSendPort());
|
| + return;
|
| + }
|
| + int index =
|
| + _File._checkReadWriteListArguments(buffer.length, offset, bytes);
|
| + if (index != 0) {
|
| + replyPort.send("index out of range in writeList: $index",
|
| + port.toSendPort());
|
| + return;
|
| + }
|
| + var result = _File._writeList(id, buffer, offset, bytes);
|
| + replyPort.send(result, port.toSendPort());
|
| + break;
|
| + case WRITE_STRING:
|
| + var id = message["id"];
|
| + var string = message["string"];
|
| + message["reply"].send(_File._writeString(id, string),
|
| + port.toSendPort());
|
| + break;
|
| + case POSITION:
|
| + message["reply"].send(_File._position(message["id"]),
|
| + port.toSendPort());
|
| + break;
|
| + case LENGTH:
|
| + message["reply"].send(_File._length(message["id"]),
|
| + port.toSendPort());
|
| + break;
|
| + case FLUSH:
|
| + message["reply"].send(_File._flush(message["id"]),
|
| + port.toSendPort());
|
| + break;
|
| + case EXIT:
|
| + port.close();
|
| + return;
|
| + }
|
| + port.receive(handleOperation);
|
| + }
|
| +
|
| + void main() {
|
| + port.receive(handleOperation);
|
| + }
|
| +}
|
| +
|
| +
|
| +class _FileOperationScheduler {
|
| + _FileOperationScheduler() : _queue = new Queue();
|
| +
|
| + void schedule(SendPort port) {
|
| + assert(_isolate != null);
|
| + if (_queue.isEmpty()) {
|
| + port.send({ "type": _FileOperationIsolate.EXIT });
|
| + _isolate = null;
|
| + } else {
|
| + port.send(_queue.removeFirst());
|
| + }
|
| + }
|
| +
|
| + void scheduleWrap(void callback(result, ignored)) {
|
| + return (result, replyTo) {
|
| + callback(result, replyTo);
|
| + schedule(replyTo);
|
| + };
|
| + }
|
| +
|
| + void enqueue(Map params, void callback(result, ignored)) {
|
| + ReceivePort replyPort = new ReceivePort.singleShot();
|
| + replyPort.receive(scheduleWrap(callback));
|
| + params["reply"] = replyPort.toSendPort();
|
| + _queue.addLast(params);
|
| + if (_isolate == null) {
|
| + _isolate = new _FileOperationIsolate();
|
| + _isolate.spawn().then((port) {
|
| + schedule(port);
|
| + });
|
| + }
|
| + }
|
| +
|
| + bool noPendingWrite() {
|
| + int queuedWrites = 0;
|
| + _queue.forEach((map) {
|
| + if (_isWriteOperation(map["type"])) {
|
| + queuedWrites++;
|
| + }
|
| + });
|
| + return queuedWrites == 0;
|
| + }
|
| +
|
| + bool _isWriteOperation(int type) {
|
| + return (type == _FileOperationIsolate.WRITE_BYTE) ||
|
| + (type == _FileOperationIsolate.WRITE_LIST) ||
|
| + (type == _FileOperationIsolate.WRITE_STRING);
|
| + }
|
| +
|
| + Queue<Map> _queue;
|
| + _FileOperationIsolate _isolate;
|
| +}
|
| +
|
| +
|
| // Class for encapsulating the native implementation of files.
|
| class _File implements File {
|
| // Constructor for file.
|
| - _File(String this._name);
|
| + _File(String this._name)
|
| + : _scheduler = new _FileOperationScheduler(),
|
| + _asyncUsed = false;
|
| +
|
| + static bool _exists(String name) native "File_Exists";
|
| + static int _open(String name, bool writable) native "File_Open";
|
| + static int _close(int id) native "File_Close";
|
| + static int _readByte(int id) native "File_ReadByte";
|
| + static int _readList(int id, List<int> buffer, int offset, int bytes)
|
| + native "File_ReadList";
|
| + static int _writeByte(int id, int value) native "File_WriteByte";
|
| + static int _writeList(int id, List<int> buffer, int offset, int bytes)
|
| + native "File_WriteList";
|
| + static int _writeString(int id, String string) native "File_WriteString";
|
| + static int _position(int id) native "File_Position";
|
| + static int _length(int id) native "File_Length";
|
| + static int _flush(int id) native "File_Flush";
|
| +
|
| + static int _checkReadWriteListArguments(int length, int offset, int bytes) {
|
| + if (offset < 0) return offset;
|
| + if (bytes < 0) return bytes;
|
| + if ((offset + bytes) > length) return offset + bytes;
|
| + return 0;
|
| + }
|
|
|
| void exists() {
|
| - throw "Unimplemented";
|
| + _asyncUsed = true;
|
| + var handler =
|
| + (_existsHandler != null) ? _existsHandler : (result) => null;
|
| + Map params = {
|
| + "type": _FileOperationIsolate.EXISTS,
|
| + "name": _name
|
| + };
|
| + _scheduler.enqueue(params, (result, ignored) { _existsHandler(result); });
|
| }
|
|
|
| bool existsSync() {
|
| - return _fileExists(_name);
|
| + if (_asyncUsed) {
|
| + throw new FileIOException(
|
| + "Mixed use of synchronous and asynchronous API");
|
| + }
|
| + return _exists(_name);
|
| }
|
|
|
| - bool _fileExists(String name) native "File_Exists";
|
| -
|
| void create() {
|
| + _asyncUsed = true;
|
| throw "Unimplemented";
|
| }
|
|
|
| void createSync() {
|
| + if (_asyncUsed) {
|
| + throw new FileIOException(
|
| + "Mixed use of synchronous and asynchronous API");
|
| + }
|
| throw "Unimplemented";
|
| }
|
|
|
| void open([bool writable = false]) {
|
| - throw "Unimplemented";
|
| + _asyncUsed = true;
|
| + var handler = (_openHandler != null) ? _openHandler : () => null;
|
| + var handleOpenResult = (result, ignored) {
|
| + if (result != 0) {
|
| + _id = result;
|
| + handler();
|
| + } else if (_errorHandler != null) {
|
| + _errorHandler("Cannot open file: $_name");
|
| + }
|
| + };
|
| + Map params = {
|
| + "type": _FileOperationIsolate.OPEN,
|
| + "name": _name,
|
| + "writable": writable
|
| + };
|
| + _scheduler.enqueue(params, handleOpenResult);
|
| }
|
|
|
| void openSync([bool writable = false]) {
|
| - if (!_openFile(_name, writable)) {
|
| + if (_asyncUsed) {
|
| + throw new FileIOException(
|
| + "Mixed use of synchronous and asynchronous API");
|
| + }
|
| + _id = _open(_name, writable);
|
| + if (_id == 0) {
|
| throw new FileIOException("Cannot open file: $_name");
|
| }
|
| }
|
|
|
| - bool _openFile(String name, bool writable) native "File_OpenFile";
|
| -
|
| void close() {
|
| - throw "Unimplemented";
|
| + _asyncUsed = true;
|
| + var handler = (_closeHandler != null) ? _closeHandler : () => null;
|
| + var handleOpenResult = (result, ignored) {
|
| + if (result != -1) {
|
| + _id = result;
|
| + handler();
|
| + } else if (_errorHandler != null) {
|
| + _errorHandler("Cannot open file: $_name");
|
| + }
|
| + };
|
| + Map params = {
|
| + "type": _FileOperationIsolate.CLOSE,
|
| + "id": _id
|
| + };
|
| + _scheduler.enqueue(params, handleOpenResult);
|
| }
|
|
|
| void closeSync() {
|
| - _close();
|
| + if (_asyncUsed) {
|
| + throw new FileIOException(
|
| + "Mixed use of synchronous and asynchronous API");
|
| + }
|
| + _id = _close(_id);
|
| + if (_id == -1) {
|
| + throw new FileIOException("Cannot close file: $_name");
|
| + }
|
| }
|
|
|
| - int _close() native "File_Close";
|
| -
|
| void readByte() {
|
| - throw "Unimplemented";
|
| + _asyncUsed = true;
|
| + var handler =
|
| + (_readByteHandler != null) ? _readByteHandler : (byte) => null;
|
| + var handleReadByteResult = (result, ignored) {
|
| + if (result != -1) {
|
| + handler(result);
|
| + } else if (_errorHandler != null) {
|
| + _errorHandler("readByte failed");
|
| + }
|
| + };
|
| + Map params = {
|
| + "type": _FileOperationIsolate.READ_BYTE,
|
| + "id": _id
|
| + };
|
| + _scheduler.enqueue(params, handleReadByteResult);
|
| }
|
|
|
| int readByteSync() {
|
| - int result = _readByte();
|
| + if (_asyncUsed) {
|
| + throw new FileIOException(
|
| + "Mixed use of synchronous and asynchronous API");
|
| + }
|
| + int result = _readByte(_id);
|
| if (result == -1) {
|
| - throw new FileIOException("Error: readByte failed");
|
| + throw new FileIOException("readByte failed");
|
| }
|
| return result;
|
| }
|
|
|
| - int _readByte() native "File_ReadByte";
|
| -
|
| void readList(List<int> buffer, int offset, int bytes) {
|
| - throw "Unimplemented";
|
| + _asyncUsed = true;
|
| + var handler =
|
| + (_readListHandler != null) ? _readListHandler : (result) => null;
|
| + var handleReadListResult = (result, ignored) {
|
| + if (result is Map && result["read"] != -1) {
|
| + var read = result["read"];
|
| + buffer.setRange(offset, read, result["buffer"]);
|
| + handler(read);
|
| + return;
|
| + }
|
| + if (_errorHandler != null) {
|
| + _errorHandler(result is String ? result : "readList failed");
|
| + }
|
| + };
|
| + Map params = {
|
| + "type": _FileOperationIsolate.READ_LIST,
|
| + "length": buffer.length,
|
| + "offset": offset,
|
| + "bytes": bytes,
|
| + "id": _id
|
| + };
|
| + _scheduler.enqueue(params, handleReadListResult);
|
| }
|
|
|
| int readListSync(List<int> buffer, int offset, int bytes) {
|
| - if (bytes == 0) {
|
| - return 0;
|
| - }
|
| - if (offset < 0) {
|
| - throw new IndexOutOfRangeException(offset);
|
| - }
|
| - if (bytes < 0) {
|
| - throw new IndexOutOfRangeException(bytes);
|
| + if (_asyncUsed) {
|
| + throw new FileIOException(
|
| + "Mixed use of synchronous and asynchronous API");
|
| }
|
| - if ((offset + bytes) > buffer.length) {
|
| - throw new IndexOutOfRangeException(offset + bytes);
|
| + if (bytes == 0) return 0;
|
| + int index = _checkReadWriteListArguments(buffer.length, offset, bytes);
|
| + if (index != 0) {
|
| + throw new IndexOutOfRangeException(index);
|
| }
|
| - int result = _readList(buffer, offset, bytes);
|
| + int result = _readList(_id, buffer, offset, bytes);
|
| if (result == -1) {
|
| - throw new FileIOException("Error: readList failed");
|
| + throw new FileIOException("readList failed");
|
| }
|
| return result;
|
| }
|
|
|
| - int _readList(List<int> buffer, int offset, int bytes) native "File_ReadList";
|
| + void _checkPendingWrites() {
|
| + if (_scheduler.noPendingWrite() && _noPendingWriteHandler != null) {
|
| + _noPendingWriteHandler();
|
| + }
|
| + }
|
|
|
| void writeByte(int value) {
|
| - throw "Unimplemented";
|
| + _asyncUsed = true;
|
| + var handleReadByteResult = (result, ignored) {
|
| + if (result == -1 &&_errorHandler != null) {
|
| + _errorHandler("writeByte failed");
|
| + return;
|
| + }
|
| + _checkPendingWrites();
|
| + };
|
| + Map params = {
|
| + "type": _FileOperationIsolate.WRITE_BYTE,
|
| + "value": value,
|
| + "id": _id
|
| + };
|
| + _scheduler.enqueue(params, handleReadByteResult);
|
| }
|
|
|
| int writeByteSync(int value) {
|
| - int result = _writeByte(value);
|
| + if (_asyncUsed) {
|
| + throw new FileIOException(
|
| + "Mixed use of synchronous and asynchronous API");
|
| + }
|
| + int result = _writeByte(_id, value);
|
| if (result == -1) {
|
| - throw new FileIOException("Error: writeByte failed");
|
| + throw new FileIOException("writeByte failed");
|
| }
|
| return result;
|
| }
|
|
|
| - int _writeByte(int value) native "File_WriteByte";
|
| -
|
| - int writeList(List<int> buffer, int offset, int bytes) {
|
| - throw "Unimplemented";
|
| + void writeList(List<int> buffer, int offset, int bytes) {
|
| + _asyncUsed = true;
|
| + var handleWriteListResult = (result, ignored) {
|
| + if (result is !String && result != -1) {
|
| + if (result < bytes) {
|
| + writeList(buffer, offset + result, bytes - result);
|
| + } else {
|
| + _checkPendingWrites();
|
| + }
|
| + return;
|
| + }
|
| + if (_errorHandler != null) {
|
| + _errorHandler(result is String ? result : "writeList failed");
|
| + }
|
| + };
|
| + Map params = {
|
| + "type": _FileOperationIsolate.WRITE_LIST,
|
| + "buffer": buffer,
|
| + "offset": offset,
|
| + "bytes": bytes,
|
| + "id": _id
|
| + };
|
| + _scheduler.enqueue(params, handleWriteListResult);
|
| }
|
|
|
| int writeListSync(List<int> buffer, int offset, int bytes) {
|
| - if (bytes == 0) {
|
| - return 0;
|
| - }
|
| - if (offset < 0) {
|
| - throw new IndexOutOfRangeException(offset);
|
| - }
|
| - if (bytes < 0) {
|
| - throw new IndexOutOfRangeException(bytes);
|
| + if (_asyncUsed) {
|
| + throw new FileIOException(
|
| + "Mixed use of synchronous and asynchronous API");
|
| }
|
| - if ((offset + bytes) > buffer.length) {
|
| - throw new IndexOutOfRangeException(offset + bytes);
|
| + if (bytes == 0) return 0;
|
| + int index = _checkReadWriteListArguments(buffer.length, offset, bytes);
|
| + if (index != 0) {
|
| + throw new IndexOutOfRangeException(index);
|
| }
|
| - int result = _writeList(buffer, offset, bytes);
|
| + int result = _writeList(_id, buffer, offset, bytes);
|
| if (result == -1) {
|
| - throw new FileIOException("Error: writeList failed");
|
| + throw new FileIOException("writeList failed");
|
| }
|
| return result;
|
| }
|
|
|
| - int _writeList(List<int> buffer, int offset, int bytes)
|
| - native "File_WriteList";
|
| -
|
| - int writeString(String string) {
|
| - throw "Unimplemented";
|
| + void writeString(String string) {
|
| + _asyncUsed = true;
|
| + var handleWriteStringResult = (result, ignored) {
|
| + if (result == -1 &&_errorHandler != null) {
|
| + _errorHandler("writeString failed");
|
| + return;
|
| + }
|
| + if (result < string.length) {
|
| + writeString(string.substring(result));
|
| + } else {
|
| + _checkPendingWrites();
|
| + }
|
| + };
|
| + Map params = {
|
| + "type": _FileOperationIsolate.WRITE_STRING,
|
| + "string": string,
|
| + "id": _id
|
| + };
|
| + _scheduler.enqueue(params, handleWriteStringResult);
|
| }
|
|
|
| int writeStringSync(String string) {
|
| - int result = _writeString(string);
|
| + if (_asyncUsed) {
|
| + throw new FileIOException(
|
| + "Mixed use of synchronous and asynchronous API");
|
| + }
|
| + int result = _writeString(_id, string);
|
| if (result == -1) {
|
| throw new FileIOException("Error: writeString failed");
|
| }
|
| return result;
|
| }
|
|
|
| - int _writeString(String string) native "File_WriteString";
|
| -
|
| - int position() {
|
| - throw "Unimplemented";
|
| + void position() {
|
| + _asyncUsed = true;
|
| + var handler = (_positionHandler != null) ? _positionHandler : (pos) => null;
|
| + var handlePositionResult = (result, ignored) {
|
| + if (result == -1 && _errorHandler != null) {
|
| + _errorHandler("position failed");
|
| + return;
|
| + }
|
| + handler(result);
|
| + };
|
| + Map params = {
|
| + "type": _FileOperationIsolate.POSITION,
|
| + "id": _id
|
| + };
|
| + _scheduler.enqueue(params, handlePositionResult);
|
| }
|
|
|
| int positionSync() {
|
| - int result = _position;
|
| + if (_asyncUsed) {
|
| + throw new FileIOException(
|
| + "Mixed use of synchronous and asynchronous API");
|
| + }
|
| + int result = _position(_id);
|
| if (result == -1) {
|
| - throw new FileIOException("Error: get position failed");
|
| + throw new FileIOException("position failed");
|
| }
|
| return result;
|
| }
|
|
|
| - int get _position() native "File_Position";
|
| -
|
| - int length() {
|
| - throw "Unimplemented";
|
| + void length() {
|
| + _asyncUsed = true;
|
| + var handler = (_lengthHandler != null) ? _lengthHandler : (pos) => null;
|
| + var handleLengthResult = (result, ignored) {
|
| + if (result == -1 && _errorHandler != null) {
|
| + _errorHandler("length failed");
|
| + return;
|
| + }
|
| + handler(result);
|
| + };
|
| + Map params = {
|
| + "type": _FileOperationIsolate.LENGTH,
|
| + "id": _id
|
| + };
|
| + _scheduler.enqueue(params, handleLengthResult);
|
| }
|
|
|
| int lengthSync() {
|
| - int result = _length;
|
| + if (_asyncUsed) {
|
| + throw new FileIOException(
|
| + "Mixed use of synchronous and asynchronous API");
|
| + }
|
| + int result = _length(_id);
|
| if (result == -1) {
|
| - throw new FileIOException("Error: get length failed");
|
| + throw new FileIOException("length failed");
|
| }
|
| return result;
|
| }
|
|
|
| - int get _length() native "File_Length";
|
| -
|
| void flush() {
|
| - throw "Unimplemented";
|
| + _asyncUsed = true;
|
| + var handler = (_flushHandler != null) ? _flushHandler : (pos) => null;
|
| + var handleFlushResult = (result, ignored) {
|
| + if (result == -1 && _errorHandler != null) {
|
| + _errorHandler("flush failed");
|
| + return;
|
| + }
|
| + handler();
|
| + };
|
| + Map params = {
|
| + "type": _FileOperationIsolate.FLUSH,
|
| + "id": _id
|
| + };
|
| + _scheduler.enqueue(params, handleFlushResult);
|
| }
|
|
|
| void flushSync() {
|
| - int result = _flush();
|
| + if (_asyncUsed) {
|
| + throw new FileIOException(
|
| + "Mixed use of synchronous and asynchronous API");
|
| + }
|
| + int result = _flush(_id);
|
| if (result == -1) {
|
| - throw new FileIOException("Error: flush failed");
|
| + throw new FileIOException("flush failed");
|
| }
|
| }
|
|
|
| - int _flush() native "File_Flush";
|
| -
|
| InputStream openInputStream() {
|
| return new _FileInputStream(this);
|
| }
|
| @@ -335,12 +707,27 @@ class _File implements File {
|
| _noPendingWriteHandler = handler;
|
| }
|
|
|
| + void set positionHandler(void handler(int pos)) {
|
| + _positionHandler = handler;
|
| + }
|
| +
|
| + void set lengthHandler(void handler(int length)) {
|
| + _lengthHandler = handler;
|
| + }
|
| +
|
| + void set flushHandler(void handler()) {
|
| + _flushHandler = handler;
|
| + }
|
| +
|
| void set errorHandler(void handler(String error)) {
|
| _errorHandler = handler;
|
| }
|
|
|
| String _name;
|
| int _id;
|
| + bool _asyncUsed;
|
| +
|
| + _FileOperationScheduler _scheduler;
|
|
|
| var _existsHandler;
|
| var _createHandler;
|
| @@ -349,5 +736,8 @@ class _File implements File {
|
| var _readByteHandler;
|
| var _readListHandler;
|
| var _noPendingWriteHandler;
|
| + var _positionHandler;
|
| + var _lengthHandler;
|
| + var _flushHandler;
|
| var _errorHandler;
|
| }
|
|
|