| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 class _FileInputStream extends _BaseDataInputStream implements InputStream { | |
| 6 _FileInputStream(String name) | |
| 7 : _data = const [], | |
| 8 _position = 0, | |
| 9 _filePosition = 0 { | |
| 10 var file = new File(name); | |
| 11 var future = file.open(FileMode.READ); | |
| 12 future.handleException((e) { | |
| 13 _reportError(e); | |
| 14 return true; | |
| 15 }); | |
| 16 future.then(_setupOpenedFile); | |
| 17 } | |
| 18 | |
| 19 _FileInputStream.fromStdio(int fd) | |
| 20 : _data = const [], | |
| 21 _position = 0, | |
| 22 _filePosition = 0 { | |
| 23 assert(fd == 0); | |
| 24 _setupOpenedFile(_File._openStdioSync(fd)); | |
| 25 } | |
| 26 | |
| 27 void _setupOpenedFile(RandomAccessFile openedFile) { | |
| 28 _openedFile = openedFile; | |
| 29 if (_streamMarkedClosed) { | |
| 30 // This input stream has already been closed. | |
| 31 _fileLength = 0; | |
| 32 _closeFile(); | |
| 33 return; | |
| 34 } | |
| 35 var futureOpen = _openedFile.length(); | |
| 36 futureOpen.then((len) { | |
| 37 _fileLength = len; | |
| 38 _fillBuffer(); | |
| 39 }); | |
| 40 futureOpen.handleException((e) { | |
| 41 _reportError(e); | |
| 42 return true; | |
| 43 }); | |
| 44 } | |
| 45 | |
| 46 void _closeFile() { | |
| 47 if (_openedFile == null) { | |
| 48 _streamMarkedClosed = true; | |
| 49 return; | |
| 50 } | |
| 51 if (available() == 0) _cancelScheduledDataCallback(); | |
| 52 if (!_openedFile.closed) { | |
| 53 _openedFile.close().then((ignore) { | |
| 54 _streamMarkedClosed = true; | |
| 55 _checkScheduleCallbacks(); | |
| 56 }); | |
| 57 } | |
| 58 } | |
| 59 | |
| 60 void _fillBuffer() { | |
| 61 Expect.equals(_position, _data.length); | |
| 62 if (_openedFile == null) return; // Called before the file is opened. | |
| 63 int size = min(_bufferLength, _fileLength - _filePosition); | |
| 64 if (size == 0) { | |
| 65 _closeFile(); | |
| 66 return; | |
| 67 } | |
| 68 // If there is currently a _fillBuffer call waiting on readList, | |
| 69 // let it fill the buffer instead of us. | |
| 70 if (_activeFillBufferCall) return; | |
| 71 _activeFillBufferCall = true; | |
| 72 if (_data.length != size) { | |
| 73 _data = new Uint8List(size); | |
| 74 // Maintain the invariant signalling that the buffer is empty. | |
| 75 _position = _data.length; | |
| 76 } | |
| 77 var future = _openedFile.readList(_data, 0, _data.length); | |
| 78 future.then((read) { | |
| 79 _filePosition += read; | |
| 80 if (read != _data.length) { | |
| 81 _data = _data.getRange(0, read); | |
| 82 } | |
| 83 _position = 0; | |
| 84 _activeFillBufferCall = false; | |
| 85 | |
| 86 if (_fileLength == _filePosition) { | |
| 87 _closeFile(); | |
| 88 } | |
| 89 _checkScheduleCallbacks(); | |
| 90 }); | |
| 91 future.handleException((e) { | |
| 92 _activeFillBufferCall = false; | |
| 93 _reportError(e); | |
| 94 return true; | |
| 95 }); | |
| 96 } | |
| 97 | |
| 98 int available() { | |
| 99 return closed ? 0 : _data.length - _position; | |
| 100 } | |
| 101 | |
| 102 void pipe(OutputStream output, {bool close: true}) { | |
| 103 _pipe(this, output, close: close); | |
| 104 } | |
| 105 | |
| 106 void _finishRead() { | |
| 107 if (_position == _data.length && !_streamMarkedClosed) { | |
| 108 _fillBuffer(); | |
| 109 } else { | |
| 110 _checkScheduleCallbacks(); | |
| 111 } | |
| 112 } | |
| 113 | |
| 114 List<int> _read(int bytesToRead) { | |
| 115 List<int> result; | |
| 116 if (_position == 0 && bytesToRead == _data.length) { | |
| 117 result = _data; | |
| 118 _data = const []; | |
| 119 } else { | |
| 120 result = new Uint8List(bytesToRead); | |
| 121 result.setRange(0, bytesToRead, _data, _position); | |
| 122 _position += bytesToRead; | |
| 123 } | |
| 124 _finishRead(); | |
| 125 return result; | |
| 126 } | |
| 127 | |
| 128 int _readInto(List<int> buffer, int offset, int len) { | |
| 129 buffer.setRange(offset, len, _data, _position); | |
| 130 _position += len; | |
| 131 _finishRead(); | |
| 132 return len; | |
| 133 } | |
| 134 | |
| 135 void _close() { | |
| 136 _data = const []; | |
| 137 _position = 0; | |
| 138 _filePosition = 0; | |
| 139 _fileLength = 0; | |
| 140 _closeFile(); | |
| 141 } | |
| 142 | |
| 143 static const int _bufferLength = 64 * 1024; | |
| 144 | |
| 145 RandomAccessFile _openedFile; | |
| 146 List<int> _data; | |
| 147 int _position; | |
| 148 int _filePosition; | |
| 149 int _fileLength; | |
| 150 bool _activeFillBufferCall = false; | |
| 151 } | |
| 152 | |
| 153 | |
| 154 class _PendingOperation { | |
| 155 const _PendingOperation(this._id); | |
| 156 static const _PendingOperation CLOSE = const _PendingOperation(0); | |
| 157 static const _PendingOperation FLUSH = const _PendingOperation(1); | |
| 158 final int _id; | |
| 159 } | |
| 160 | |
| 161 | |
| 162 class _FileOutputStream extends _BaseOutputStream implements OutputStream { | |
| 163 _FileOutputStream(String name, FileMode mode) { | |
| 164 _pendingOperations = new List(); | |
| 165 var f = new File(name); | |
| 166 var openFuture = f.open(mode); | |
| 167 openFuture.then((openedFile) { | |
| 168 _file = openedFile; | |
| 169 _processPendingOperations(); | |
| 170 }); | |
| 171 openFuture.handleException((e) { | |
| 172 _reportError(e); | |
| 173 return true; | |
| 174 }); | |
| 175 } | |
| 176 | |
| 177 _FileOutputStream.fromStdio(int fd) { | |
| 178 assert(1 <= fd && fd <= 2); | |
| 179 _file = _File._openStdioSync(fd); | |
| 180 } | |
| 181 | |
| 182 bool write(List<int> buffer, [bool copyBuffer = false]) { | |
| 183 var data = buffer; | |
| 184 if (copyBuffer) { | |
| 185 var length = buffer.length; | |
| 186 data = new Uint8List(length); | |
| 187 data.setRange(0, length, buffer, 0); | |
| 188 } | |
| 189 if (_file == null) { | |
| 190 _pendingOperations.add(data); | |
| 191 } else { | |
| 192 _write(data, 0, data.length); | |
| 193 } | |
| 194 return false; | |
| 195 } | |
| 196 | |
| 197 bool writeFrom(List<int> buffer, [int offset = 0, int len]) { | |
| 198 // A copy is required by the interface. | |
| 199 var length = buffer.length - offset; | |
| 200 if (len != null) { | |
| 201 if (len > length) throw new IndexOutOfRangeException(len); | |
| 202 length = len; | |
| 203 } | |
| 204 var copy = new Uint8List(length); | |
| 205 copy.setRange(0, length, buffer, offset); | |
| 206 return write(copy); | |
| 207 } | |
| 208 | |
| 209 | |
| 210 void flush() { | |
| 211 if (_file == null) { | |
| 212 _pendingOperations.add(_PendingOperation.FLUSH); | |
| 213 } else { | |
| 214 _file.flush().then((ignored) => null); | |
| 215 } | |
| 216 } | |
| 217 | |
| 218 | |
| 219 void close() { | |
| 220 _streamMarkedClosed = true; | |
| 221 if (_file == null) { | |
| 222 _pendingOperations.add(_PendingOperation.CLOSE); | |
| 223 } else if (!_closeCallbackScheduled) { | |
| 224 _file.close().then((ignore) { | |
| 225 if (_onClosed != null) _onClosed(); | |
| 226 }); | |
| 227 _closeCallbackScheduled = true; | |
| 228 } | |
| 229 } | |
| 230 | |
| 231 void set onNoPendingWrites(void callback()) { | |
| 232 _onNoPendingWrites = callback; | |
| 233 if ((_pendingOperations == null || _pendingOperations.length == 0) && | |
| 234 outstandingWrites == 0 && | |
| 235 !_streamMarkedClosed && | |
| 236 _onNoPendingWrites != null) { | |
| 237 new Timer(0, (t) { | |
| 238 if (_onNoPendingWrites != null) { | |
| 239 _onNoPendingWrites(); | |
| 240 } | |
| 241 }); | |
| 242 } | |
| 243 } | |
| 244 | |
| 245 void set onClosed(void callback()) { | |
| 246 _onClosed = callback; | |
| 247 } | |
| 248 | |
| 249 void _processPendingOperations() { | |
| 250 _pendingOperations.forEach((buffer) { | |
| 251 if (buffer is _PendingOperation) { | |
| 252 if (buffer === _PendingOperation.CLOSE) { | |
| 253 close(); | |
| 254 } else { | |
| 255 assert(buffer === _PendingOperation.FLUSH); | |
| 256 flush(); | |
| 257 } | |
| 258 } else { | |
| 259 write(buffer); | |
| 260 } | |
| 261 }); | |
| 262 _pendingOperations = null; | |
| 263 } | |
| 264 | |
| 265 void _write(List<int> buffer, int offset, int len) { | |
| 266 outstandingWrites++; | |
| 267 var writeListFuture = _file.writeList(buffer, offset, len); | |
| 268 writeListFuture.then((ignore) { | |
| 269 outstandingWrites--; | |
| 270 if (outstandingWrites == 0 && | |
| 271 !_streamMarkedClosed && | |
| 272 _onNoPendingWrites != null) { | |
| 273 _onNoPendingWrites(); | |
| 274 } | |
| 275 }); | |
| 276 writeListFuture.handleException((e) { | |
| 277 outstandingWrites--; | |
| 278 _reportError(e); | |
| 279 return true; | |
| 280 }); | |
| 281 } | |
| 282 | |
| 283 bool get closed => _streamMarkedClosed; | |
| 284 | |
| 285 RandomAccessFile _file; | |
| 286 | |
| 287 // When this is set to true the stream is marked closed. When a | |
| 288 // stream is marked closed no more data can be written. | |
| 289 bool _streamMarkedClosed = false; | |
| 290 | |
| 291 // When this is set to true, the close callback has been scheduled and the | |
| 292 // stream will be fully closed once it's called. | |
| 293 bool _closeCallbackScheduled = false; | |
| 294 | |
| 295 // Number of writes that have not yet completed. | |
| 296 int outstandingWrites = 0; | |
| 297 | |
| 298 // List of pending writes that were issued before the underlying | |
| 299 // file was successfully opened. | |
| 300 List _pendingOperations; | |
| 301 | |
| 302 Function _onNoPendingWrites; | |
| 303 Function _onClosed; | |
| 304 } | |
| 305 | |
| 306 const int _EXISTS_REQUEST = 0; | |
| 307 const int _CREATE_REQUEST = 1; | |
| 308 const int _DELETE_REQUEST = 2; | |
| 309 const int _OPEN_REQUEST = 3; | |
| 310 const int _FULL_PATH_REQUEST = 4; | |
| 311 const int _DIRECTORY_REQUEST = 5; | |
| 312 const int _CLOSE_REQUEST = 6; | |
| 313 const int _POSITION_REQUEST = 7; | |
| 314 const int _SET_POSITION_REQUEST = 8; | |
| 315 const int _TRUNCATE_REQUEST = 9; | |
| 316 const int _LENGTH_REQUEST = 10; | |
| 317 const int _LENGTH_FROM_NAME_REQUEST = 11; | |
| 318 const int _LAST_MODIFIED_REQUEST = 12; | |
| 319 const int _FLUSH_REQUEST = 13; | |
| 320 const int _READ_BYTE_REQUEST = 14; | |
| 321 const int _WRITE_BYTE_REQUEST = 15; | |
| 322 const int _READ_LIST_REQUEST = 16; | |
| 323 const int _WRITE_LIST_REQUEST = 17; | |
| 324 const int _WRITE_STRING_REQUEST = 18; | |
| 325 | |
| 326 // Base class for _File and _RandomAccessFile with shared functions. | |
| 327 class _FileBase { | |
| 328 bool _isErrorResponse(response) { | |
| 329 return response is List && response[0] != _SUCCESS_RESPONSE; | |
| 330 } | |
| 331 | |
| 332 Exception _exceptionFromResponse(response, String message) { | |
| 333 assert(_isErrorResponse(response)); | |
| 334 switch (response[_ERROR_RESPONSE_ERROR_TYPE]) { | |
| 335 case _ILLEGAL_ARGUMENT_RESPONSE: | |
| 336 return new ArgumentError(); | |
| 337 case _OSERROR_RESPONSE: | |
| 338 var err = new OSError(response[_OSERROR_RESPONSE_MESSAGE], | |
| 339 response[_OSERROR_RESPONSE_ERROR_CODE]); | |
| 340 return new FileIOException(message, err); | |
| 341 case _FILE_CLOSED_RESPONSE: | |
| 342 return new FileIOException("File closed"); | |
| 343 default: | |
| 344 return new Exception("Unknown error"); | |
| 345 } | |
| 346 } | |
| 347 } | |
| 348 | |
| 349 SendPort _newServicePort() native "File_NewServicePort"; | |
| 350 | |
| 351 // Class for encapsulating the native implementation of files. | |
| 352 class _File extends _FileBase implements File { | |
| 353 // Constructor for file. | |
| 354 _File(String this._name) { | |
| 355 if (_name is! String) { | |
| 356 throw new ArgumentError('${NoSuchMethodError.safeToString(_name)} ' | |
| 357 'is not a String'); | |
| 358 } | |
| 359 } | |
| 360 | |
| 361 // Constructor from Path for file. | |
| 362 _File.fromPath(Path path) : this(path.toNativePath()); | |
| 363 | |
| 364 Future<bool> exists() { | |
| 365 _ensureFileService(); | |
| 366 List request = new List(2); | |
| 367 request[0] = _EXISTS_REQUEST; | |
| 368 request[1] = _name; | |
| 369 return _fileService.call(request).transform((response) { | |
| 370 if (_isErrorResponse(response)) { | |
| 371 throw _exceptionFromResponse(response, "Cannot open file '$_name'"); | |
| 372 } | |
| 373 return response; | |
| 374 }); | |
| 375 } | |
| 376 | |
| 377 | |
| 378 static _exists(String name) native "File_Exists"; | |
| 379 | |
| 380 bool existsSync() { | |
| 381 var result = _exists(_name); | |
| 382 throwIfError(result, "Cannot check existence of file '$_name'"); | |
| 383 return result; | |
| 384 } | |
| 385 | |
| 386 Future<File> create() { | |
| 387 _ensureFileService(); | |
| 388 List request = new List(2); | |
| 389 request[0] = _CREATE_REQUEST; | |
| 390 request[1] = _name; | |
| 391 return _fileService.call(request).transform((response) { | |
| 392 if (_isErrorResponse(response)) { | |
| 393 throw _exceptionFromResponse(response, "Cannot create file '$_name'"); | |
| 394 } | |
| 395 return this; | |
| 396 }); | |
| 397 } | |
| 398 | |
| 399 static _create(String name) native "File_Create"; | |
| 400 | |
| 401 void createSync() { | |
| 402 var result = _create(_name); | |
| 403 throwIfError(result, "Cannot create file '$_name'"); | |
| 404 } | |
| 405 | |
| 406 Future<File> delete() { | |
| 407 _ensureFileService(); | |
| 408 List request = new List(2); | |
| 409 request[0] = _DELETE_REQUEST; | |
| 410 request[1] = _name; | |
| 411 return _fileService.call(request).transform((response) { | |
| 412 if (_isErrorResponse(response)) { | |
| 413 throw _exceptionFromResponse(response, "Cannot delete file '$_name'"); | |
| 414 } | |
| 415 return this; | |
| 416 }); | |
| 417 } | |
| 418 | |
| 419 static _delete(String name) native "File_Delete"; | |
| 420 | |
| 421 void deleteSync() { | |
| 422 var result = _delete(_name); | |
| 423 throwIfError(result, "Cannot delete file '$_name'"); | |
| 424 } | |
| 425 | |
| 426 Future<Directory> directory() { | |
| 427 _ensureFileService(); | |
| 428 List request = new List(2); | |
| 429 request[0] = _DIRECTORY_REQUEST; | |
| 430 request[1] = _name; | |
| 431 return _fileService.call(request).transform((response) { | |
| 432 if (_isErrorResponse(response)) { | |
| 433 throw _exceptionFromResponse(response, | |
| 434 "Cannot retrieve directory for " | |
| 435 "file '$_name'"); | |
| 436 } | |
| 437 return new Directory(response); | |
| 438 }); | |
| 439 } | |
| 440 | |
| 441 static _directory(String name) native "File_Directory"; | |
| 442 | |
| 443 Directory directorySync() { | |
| 444 var result = _directory(name); | |
| 445 throwIfError(result, "Cannot retrieve directory for file '$_name'"); | |
| 446 return new Directory(result); | |
| 447 } | |
| 448 | |
| 449 Future<RandomAccessFile> open([FileMode mode = FileMode.READ]) { | |
| 450 _ensureFileService(); | |
| 451 Completer<RandomAccessFile> completer = new Completer<RandomAccessFile>(); | |
| 452 if (mode != FileMode.READ && | |
| 453 mode != FileMode.WRITE && | |
| 454 mode != FileMode.APPEND) { | |
| 455 new Timer(0, (t) { | |
| 456 completer.completeException(new ArgumentError()); | |
| 457 }); | |
| 458 return completer.future; | |
| 459 } | |
| 460 List request = new List(3); | |
| 461 request[0] = _OPEN_REQUEST; | |
| 462 request[1] = _name; | |
| 463 request[2] = mode._mode; // Direct int value for serialization. | |
| 464 return _fileService.call(request).transform((response) { | |
| 465 if (_isErrorResponse(response)) { | |
| 466 throw _exceptionFromResponse(response, "Cannot open file '$_name'"); | |
| 467 } | |
| 468 return new _RandomAccessFile(response, _name); | |
| 469 }); | |
| 470 } | |
| 471 | |
| 472 Future<int> length() { | |
| 473 _ensureFileService(); | |
| 474 List request = new List(2); | |
| 475 request[0] = _LENGTH_FROM_NAME_REQUEST; | |
| 476 request[1] = _name; | |
| 477 return _fileService.call(request).transform((response) { | |
| 478 if (_isErrorResponse(response)) { | |
| 479 throw _exceptionFromResponse(response, | |
| 480 "Cannot retrieve length of " | |
| 481 "file '$_name'"); | |
| 482 } | |
| 483 return response; | |
| 484 }); | |
| 485 } | |
| 486 | |
| 487 | |
| 488 static _lengthFromName(String name) native "File_LengthFromName"; | |
| 489 | |
| 490 int lengthSync() { | |
| 491 var result = _lengthFromName(_name); | |
| 492 throwIfError(result, "Cannot retrieve length of file '$_name'"); | |
| 493 return result; | |
| 494 } | |
| 495 | |
| 496 Future<Date> lastModified() { | |
| 497 _ensureFileService(); | |
| 498 List request = new List(2); | |
| 499 request[0] = _LAST_MODIFIED_REQUEST; | |
| 500 request[1] = _name; | |
| 501 return _fileService.call(request).transform((response) { | |
| 502 if (_isErrorResponse(response)) { | |
| 503 throw _exceptionFromResponse(response, | |
| 504 "Cannot retrieve modification time " | |
| 505 "for file '$_name'"); | |
| 506 } | |
| 507 return new Date.fromMillisecondsSinceEpoch(response); | |
| 508 }); | |
| 509 } | |
| 510 | |
| 511 static _lastModified(String name) native "File_LastModified"; | |
| 512 | |
| 513 Date lastModifiedSync() { | |
| 514 var ms = _lastModified(name); | |
| 515 throwIfError(ms, "Cannot retrieve modification time for file '$_name'"); | |
| 516 return new Date.fromMillisecondsSinceEpoch(ms); | |
| 517 } | |
| 518 | |
| 519 static _open(String name, int mode) native "File_Open"; | |
| 520 | |
| 521 RandomAccessFile openSync([FileMode mode = FileMode.READ]) { | |
| 522 if (mode != FileMode.READ && | |
| 523 mode != FileMode.WRITE && | |
| 524 mode != FileMode.APPEND) { | |
| 525 throw new FileIOException("Unknown file mode. Use FileMode.READ, " | |
| 526 "FileMode.WRITE or FileMode.APPEND."); | |
| 527 } | |
| 528 var id = _open(_name, mode._mode); | |
| 529 throwIfError(id, "Cannot open file '$_name'"); | |
| 530 return new _RandomAccessFile(id, _name); | |
| 531 } | |
| 532 | |
| 533 static int _openStdio(int fd) native "File_OpenStdio"; | |
| 534 | |
| 535 static RandomAccessFile _openStdioSync(int fd) { | |
| 536 var id = _openStdio(fd); | |
| 537 if (id == 0) { | |
| 538 throw new FileIOException("Cannot open stdio file for: $fd"); | |
| 539 } | |
| 540 return new _RandomAccessFile(id, ""); | |
| 541 } | |
| 542 | |
| 543 Future<String> fullPath() { | |
| 544 _ensureFileService(); | |
| 545 List request = new List(2); | |
| 546 request[0] = _FULL_PATH_REQUEST; | |
| 547 request[1] = _name; | |
| 548 return _fileService.call(request).transform((response) { | |
| 549 if (_isErrorResponse(response)) { | |
| 550 throw _exceptionFromResponse(response, | |
| 551 "Cannot retrieve full path" | |
| 552 " for '$_name'"); | |
| 553 } | |
| 554 return response; | |
| 555 }); | |
| 556 } | |
| 557 | |
| 558 static _fullPath(String name) native "File_FullPath"; | |
| 559 | |
| 560 String fullPathSync() { | |
| 561 var result = _fullPath(_name); | |
| 562 throwIfError(result, "Cannot retrieve full path for file '$_name'"); | |
| 563 return result; | |
| 564 } | |
| 565 | |
| 566 InputStream openInputStream() { | |
| 567 return new _FileInputStream(_name); | |
| 568 } | |
| 569 | |
| 570 OutputStream openOutputStream([FileMode mode = FileMode.WRITE]) { | |
| 571 if (mode != FileMode.WRITE && | |
| 572 mode != FileMode.APPEND) { | |
| 573 throw new FileIOException( | |
| 574 "Wrong FileMode. Use FileMode.WRITE or FileMode.APPEND"); | |
| 575 } | |
| 576 return new _FileOutputStream(_name, mode); | |
| 577 } | |
| 578 | |
| 579 Future<List<int>> readAsBytes() { | |
| 580 _ensureFileService(); | |
| 581 Completer<List<int>> completer = new Completer<List<int>>(); | |
| 582 var chunks = new _BufferList(); | |
| 583 var stream = openInputStream(); | |
| 584 stream.onClosed = () { | |
| 585 var result = chunks.readBytes(chunks.length); | |
| 586 if (result == null) result = <int>[]; | |
| 587 completer.complete(result); | |
| 588 }; | |
| 589 stream.onData = () { | |
| 590 var chunk = stream.read(); | |
| 591 chunks.add(chunk); | |
| 592 }; | |
| 593 stream.onError = completer.completeException; | |
| 594 return completer.future; | |
| 595 } | |
| 596 | |
| 597 List<int> readAsBytesSync() { | |
| 598 var opened = openSync(); | |
| 599 var length = opened.lengthSync(); | |
| 600 var result = new Uint8List(length); | |
| 601 var read = opened.readListSync(result, 0, length); | |
| 602 if (read != length) { | |
| 603 throw new FileIOException("Failed to read file"); | |
| 604 } | |
| 605 opened.closeSync(); | |
| 606 return result; | |
| 607 } | |
| 608 | |
| 609 Future<String> readAsText([Encoding encoding = Encoding.UTF_8]) { | |
| 610 _ensureFileService(); | |
| 611 return readAsBytes().transform((bytes) { | |
| 612 if (bytes.length == 0) return ""; | |
| 613 var decoder = _StringDecoders.decoder(encoding); | |
| 614 decoder.write(bytes); | |
| 615 return decoder.decoded(); | |
| 616 }); | |
| 617 } | |
| 618 | |
| 619 String readAsTextSync([Encoding encoding = Encoding.UTF_8]) { | |
| 620 var decoder = _StringDecoders.decoder(encoding); | |
| 621 List<int> bytes = readAsBytesSync(); | |
| 622 if (bytes.length == 0) return ""; | |
| 623 decoder.write(bytes); | |
| 624 return decoder.decoded(); | |
| 625 } | |
| 626 | |
| 627 List<String> _getDecodedLines(_StringDecoder decoder) { | |
| 628 List<String> result = []; | |
| 629 var line = decoder.decodedLine; | |
| 630 while (line != null) { | |
| 631 result.add(line); | |
| 632 line = decoder.decodedLine; | |
| 633 } | |
| 634 // If there is more data with no terminating line break we treat | |
| 635 // it as the last line. | |
| 636 var data = decoder.decoded(); | |
| 637 if (data != null) { | |
| 638 result.add(data); | |
| 639 } | |
| 640 return result; | |
| 641 } | |
| 642 | |
| 643 Future<List<String>> readAsLines([Encoding encoding = Encoding.UTF_8]) { | |
| 644 _ensureFileService(); | |
| 645 Completer<List<String>> completer = new Completer<List<String>>(); | |
| 646 return readAsBytes().transform((bytes) { | |
| 647 var decoder = _StringDecoders.decoder(encoding); | |
| 648 decoder.write(bytes); | |
| 649 return _getDecodedLines(decoder); | |
| 650 }); | |
| 651 } | |
| 652 | |
| 653 List<String> readAsLinesSync([Encoding encoding = Encoding.UTF_8]) { | |
| 654 var decoder = _StringDecoders.decoder(encoding); | |
| 655 List<int> bytes = readAsBytesSync(); | |
| 656 decoder.write(bytes); | |
| 657 return _getDecodedLines(decoder); | |
| 658 } | |
| 659 | |
| 660 String get name => _name; | |
| 661 | |
| 662 void _ensureFileService() { | |
| 663 if (_fileService == null) { | |
| 664 _fileService = _newServicePort(); | |
| 665 } | |
| 666 } | |
| 667 | |
| 668 static throwIfError(Object result, String msg) { | |
| 669 if (result is OSError) { | |
| 670 throw new FileIOException(msg, result); | |
| 671 } | |
| 672 } | |
| 673 | |
| 674 final String _name; | |
| 675 | |
| 676 SendPort _fileService; | |
| 677 } | |
| 678 | |
| 679 | |
| 680 class _RandomAccessFile extends _FileBase implements RandomAccessFile { | |
| 681 _RandomAccessFile(int this._id, String this._name); | |
| 682 | |
| 683 Future<RandomAccessFile> close() { | |
| 684 Completer<RandomAccessFile> completer = new Completer<RandomAccessFile>(); | |
| 685 if (closed) return _completeWithClosedException(completer); | |
| 686 _ensureFileService(); | |
| 687 List request = new List(2); | |
| 688 request[0] = _CLOSE_REQUEST; | |
| 689 request[1] = _id; | |
| 690 // Set the id_ to 0 (NULL) to ensure the no more async requests | |
| 691 // can be issued for this file. | |
| 692 _id = 0; | |
| 693 return _fileService.call(request).transform((result) { | |
| 694 if (result != -1) { | |
| 695 _id = result; | |
| 696 return this; | |
| 697 } else { | |
| 698 throw new FileIOException("Cannot close file '$_name'"); | |
| 699 } | |
| 700 }); | |
| 701 } | |
| 702 | |
| 703 static int _close(int id) native "File_Close"; | |
| 704 | |
| 705 void closeSync() { | |
| 706 _checkNotClosed(); | |
| 707 var id = _close(_id); | |
| 708 if (id == -1) { | |
| 709 throw new FileIOException("Cannot close file '$_name'"); | |
| 710 } | |
| 711 _id = id; | |
| 712 } | |
| 713 | |
| 714 Future<int> readByte() { | |
| 715 _ensureFileService(); | |
| 716 Completer<int> completer = new Completer<int>(); | |
| 717 if (closed) return _completeWithClosedException(completer); | |
| 718 List request = new List(2); | |
| 719 request[0] = _READ_BYTE_REQUEST; | |
| 720 request[1] = _id; | |
| 721 return _fileService.call(request).transform((response) { | |
| 722 if (_isErrorResponse(response)) { | |
| 723 throw _exceptionFromResponse(response, | |
| 724 "readByte failed for file '$_name'"); | |
| 725 } | |
| 726 return response; | |
| 727 }); | |
| 728 } | |
| 729 | |
| 730 static _readByte(int id) native "File_ReadByte"; | |
| 731 | |
| 732 int readByteSync() { | |
| 733 _checkNotClosed(); | |
| 734 var result = _readByte(_id); | |
| 735 if (result is OSError) { | |
| 736 throw new FileIOException("readByte failed for file '$_name'", result); | |
| 737 } | |
| 738 return result; | |
| 739 } | |
| 740 | |
| 741 Future<int> readList(List<int> buffer, int offset, int bytes) { | |
| 742 _ensureFileService(); | |
| 743 Completer<int> completer = new Completer<int>(); | |
| 744 if (buffer is !List || offset is !int || bytes is !int) { | |
| 745 // Complete asynchronously so the user has a chance to setup | |
| 746 // handlers without getting exceptions when registering the | |
| 747 // then handler. | |
| 748 new Timer(0, (t) { | |
| 749 completer.completeException(new FileIOException( | |
| 750 "Invalid arguments to readList for file '$_name'")); | |
| 751 }); | |
| 752 return completer.future; | |
| 753 }; | |
| 754 if (closed) return _completeWithClosedException(completer); | |
| 755 List request = new List(3); | |
| 756 request[0] = _READ_LIST_REQUEST; | |
| 757 request[1] = _id; | |
| 758 request[2] = bytes; | |
| 759 return _fileService.call(request).transform((response) { | |
| 760 if (_isErrorResponse(response)) { | |
| 761 throw _exceptionFromResponse(response, | |
| 762 "readList failed for file '$_name'"); | |
| 763 } | |
| 764 var read = response[1]; | |
| 765 var data = response[2]; | |
| 766 buffer.setRange(offset, read, data); | |
| 767 return read; | |
| 768 }); | |
| 769 } | |
| 770 | |
| 771 static void _checkReadWriteListArguments(int length, int offset, int bytes) { | |
| 772 if (offset < 0) throw new IndexOutOfRangeException(offset); | |
| 773 if (bytes < 0) throw new IndexOutOfRangeException(bytes); | |
| 774 if ((offset + bytes) > length) { | |
| 775 throw new IndexOutOfRangeException(offset + bytes); | |
| 776 } | |
| 777 } | |
| 778 | |
| 779 static _readList(int id, List<int> buffer, int offset, int bytes) | |
| 780 native "File_ReadList"; | |
| 781 | |
| 782 int readListSync(List<int> buffer, int offset, int bytes) { | |
| 783 _checkNotClosed(); | |
| 784 if (buffer is !List || offset is !int || bytes is !int) { | |
| 785 throw new FileIOException( | |
| 786 "Invalid arguments to readList for file '$_name'"); | |
| 787 } | |
| 788 if (bytes == 0) return 0; | |
| 789 _checkReadWriteListArguments(buffer.length, offset, bytes); | |
| 790 var result = _readList(_id, buffer, offset, bytes); | |
| 791 if (result is OSError) { | |
| 792 throw new FileIOException("readList failed for file '$_name'", | |
| 793 result); | |
| 794 } | |
| 795 return result; | |
| 796 } | |
| 797 | |
| 798 Future<RandomAccessFile> writeByte(int value) { | |
| 799 _ensureFileService(); | |
| 800 Completer<RandomAccessFile> completer = new Completer<RandomAccessFile>(); | |
| 801 if (value is !int) { | |
| 802 // Complete asynchronously so the user has a chance to setup | |
| 803 // handlers without getting exceptions when registering the | |
| 804 // then handler. | |
| 805 new Timer(0, (t) { | |
| 806 completer.completeException(new FileIOException( | |
| 807 "Invalid argument to writeByte for file '$_name'")); | |
| 808 }); | |
| 809 return completer.future; | |
| 810 } | |
| 811 if (closed) return _completeWithClosedException(completer); | |
| 812 List request = new List(3); | |
| 813 request[0] = _WRITE_BYTE_REQUEST; | |
| 814 request[1] = _id; | |
| 815 request[2] = value; | |
| 816 return _fileService.call(request).transform((response) { | |
| 817 if (_isErrorResponse(response)) { | |
| 818 throw _exceptionFromResponse(response, | |
| 819 "writeByte failed for file '$_name'"); | |
| 820 } | |
| 821 return this; | |
| 822 }); | |
| 823 } | |
| 824 | |
| 825 static _writeByte(int id, int value) native "File_WriteByte"; | |
| 826 | |
| 827 int writeByteSync(int value) { | |
| 828 _checkNotClosed(); | |
| 829 if (value is !int) { | |
| 830 throw new FileIOException( | |
| 831 "Invalid argument to writeByte for file '$_name'"); | |
| 832 } | |
| 833 var result = _writeByte(_id, value); | |
| 834 if (result is OSError) { | |
| 835 throw new FileIOException("writeByte failed for file '$_name'", | |
| 836 result); | |
| 837 } | |
| 838 return result; | |
| 839 } | |
| 840 | |
| 841 Future<RandomAccessFile> writeList(List<int> buffer, int offset, int bytes) { | |
| 842 _ensureFileService(); | |
| 843 Completer<RandomAccessFile> completer = new Completer<RandomAccessFile>(); | |
| 844 if (buffer is !List || offset is !int || bytes is !int) { | |
| 845 // Complete asynchronously so the user has a chance to setup | |
| 846 // handlers without getting exceptions when registering the | |
| 847 // then handler. | |
| 848 new Timer(0, (t) { | |
| 849 completer.completeException(new FileIOException( | |
| 850 "Invalid arguments to writeList for file '$_name'")); | |
| 851 }); | |
| 852 return completer.future; | |
| 853 } | |
| 854 if (closed) return _completeWithClosedException(completer); | |
| 855 | |
| 856 _BufferAndOffset result; | |
| 857 try { | |
| 858 result = _ensureFastAndSerializableBuffer(buffer, offset, bytes); | |
| 859 } catch (e) { | |
| 860 // Complete asynchronously so the user has a chance to setup | |
| 861 // handlers without getting exceptions when registering the | |
| 862 // then handler. | |
| 863 new Timer(0, (t) => completer.completeException(e)); | |
| 864 return completer.future; | |
| 865 } | |
| 866 | |
| 867 List request = new List(5); | |
| 868 request[0] = _WRITE_LIST_REQUEST; | |
| 869 request[1] = _id; | |
| 870 request[2] = result.buffer; | |
| 871 request[3] = result.offset; | |
| 872 request[4] = bytes; | |
| 873 return _fileService.call(request).transform((response) { | |
| 874 if (_isErrorResponse(response)) { | |
| 875 throw _exceptionFromResponse(response, | |
| 876 "writeList failed for file '$_name'"); | |
| 877 } | |
| 878 return this; | |
| 879 }); | |
| 880 } | |
| 881 | |
| 882 static _writeList(int id, List<int> buffer, int offset, int bytes) | |
| 883 native "File_WriteList"; | |
| 884 | |
| 885 int writeListSync(List<int> buffer, int offset, int bytes) { | |
| 886 _checkNotClosed(); | |
| 887 if (buffer is !List || offset is !int || bytes is !int) { | |
| 888 throw new FileIOException( | |
| 889 "Invalid arguments to writeList for file '$_name'"); | |
| 890 } | |
| 891 if (bytes == 0) return 0; | |
| 892 _checkReadWriteListArguments(buffer.length, offset, bytes); | |
| 893 _BufferAndOffset bufferAndOffset = | |
| 894 _ensureFastAndSerializableBuffer(buffer, offset, bytes); | |
| 895 var result = | |
| 896 _writeList(_id, bufferAndOffset.buffer, bufferAndOffset.offset, bytes); | |
| 897 if (result is OSError) { | |
| 898 throw new FileIOException("writeList failed for file '$_name'", result); | |
| 899 } | |
| 900 return result; | |
| 901 } | |
| 902 | |
| 903 Future<RandomAccessFile> writeString(String string, | |
| 904 [Encoding encoding = Encoding.UTF_8]) { | |
| 905 _ensureFileService(); | |
| 906 Completer<RandomAccessFile> completer = new Completer<RandomAccessFile>(); | |
| 907 if (closed) return _completeWithClosedException(completer); | |
| 908 List request = new List(3); | |
| 909 request[0] = _WRITE_STRING_REQUEST; | |
| 910 request[1] = _id; | |
| 911 request[2] = string; | |
| 912 return _fileService.call(request).transform((response) { | |
| 913 if (_isErrorResponse(response)) { | |
| 914 throw _exceptionFromResponse(response, | |
| 915 "writeString failed for file '$_name'"); | |
| 916 } | |
| 917 return this; | |
| 918 }); | |
| 919 } | |
| 920 | |
| 921 static _writeString(int id, String string) native "File_WriteString"; | |
| 922 | |
| 923 int writeStringSync(String string, [Encoding encoding = Encoding.UTF_8]) { | |
| 924 _checkNotClosed(); | |
| 925 if (string is !String) throw new ArgumentError(); | |
| 926 var result = _writeString(_id, string); | |
| 927 if (result is OSError) { | |
| 928 throw new FileIOException("writeString failed for file '$_name'"); | |
| 929 } | |
| 930 return result; | |
| 931 } | |
| 932 | |
| 933 Future<int> position() { | |
| 934 _ensureFileService(); | |
| 935 Completer<int> completer = new Completer<int>(); | |
| 936 if (closed) return _completeWithClosedException(completer); | |
| 937 List request = new List(2); | |
| 938 request[0] = _POSITION_REQUEST; | |
| 939 request[1] = _id; | |
| 940 return _fileService.call(request).transform((response) { | |
| 941 if (_isErrorResponse(response)) { | |
| 942 throw _exceptionFromResponse(response, | |
| 943 "position failed for file '$_name'"); | |
| 944 } | |
| 945 return response; | |
| 946 }); | |
| 947 } | |
| 948 | |
| 949 static _position(int id) native "File_Position"; | |
| 950 | |
| 951 int positionSync() { | |
| 952 _checkNotClosed(); | |
| 953 var result = _position(_id); | |
| 954 if (result is OSError) { | |
| 955 throw new FileIOException("position failed for file '$_name'", result); | |
| 956 } | |
| 957 return result; | |
| 958 } | |
| 959 | |
| 960 Future<RandomAccessFile> setPosition(int position) { | |
| 961 _ensureFileService(); | |
| 962 Completer<RandomAccessFile> completer = new Completer<RandomAccessFile>(); | |
| 963 if (closed) return _completeWithClosedException(completer); | |
| 964 List request = new List(3); | |
| 965 request[0] = _SET_POSITION_REQUEST; | |
| 966 request[1] = _id; | |
| 967 request[2] = position; | |
| 968 return _fileService.call(request).transform((response) { | |
| 969 if (_isErrorResponse(response)) { | |
| 970 throw _exceptionFromResponse(response, | |
| 971 "setPosition failed for file '$_name'"); | |
| 972 } | |
| 973 return this; | |
| 974 }); | |
| 975 } | |
| 976 | |
| 977 static _setPosition(int id, int position) native "File_SetPosition"; | |
| 978 | |
| 979 void setPositionSync(int position) { | |
| 980 _checkNotClosed(); | |
| 981 var result = _setPosition(_id, position); | |
| 982 if (result is OSError) { | |
| 983 throw new FileIOException("setPosition failed for file '$_name'", result); | |
| 984 } | |
| 985 } | |
| 986 | |
| 987 Future<RandomAccessFile> truncate(int length) { | |
| 988 _ensureFileService(); | |
| 989 Completer<RandomAccessFile> completer = new Completer<RandomAccessFile>(); | |
| 990 if (closed) return _completeWithClosedException(completer); | |
| 991 List request = new List(3); | |
| 992 request[0] = _TRUNCATE_REQUEST; | |
| 993 request[1] = _id; | |
| 994 request[2] = length; | |
| 995 return _fileService.call(request).transform((response) { | |
| 996 if (_isErrorResponse(response)) { | |
| 997 throw _exceptionFromResponse(response, | |
| 998 "truncate failed for file '$_name'"); | |
| 999 } | |
| 1000 return this; | |
| 1001 }); | |
| 1002 } | |
| 1003 | |
| 1004 static _truncate(int id, int length) native "File_Truncate"; | |
| 1005 | |
| 1006 void truncateSync(int length) { | |
| 1007 _checkNotClosed(); | |
| 1008 var result = _truncate(_id, length); | |
| 1009 if (result is OSError) { | |
| 1010 throw new FileIOException("truncate failed for file '$_name'", result); | |
| 1011 } | |
| 1012 } | |
| 1013 | |
| 1014 Future<int> length() { | |
| 1015 _ensureFileService(); | |
| 1016 Completer<int> completer = new Completer<int>(); | |
| 1017 if (closed) return _completeWithClosedException(completer); | |
| 1018 List request = new List(2); | |
| 1019 request[0] = _LENGTH_REQUEST; | |
| 1020 request[1] = _id; | |
| 1021 return _fileService.call(request).transform((response) { | |
| 1022 if (_isErrorResponse(response)) { | |
| 1023 throw _exceptionFromResponse(response, | |
| 1024 "length failed for file '$_name'"); | |
| 1025 } | |
| 1026 return response; | |
| 1027 }); | |
| 1028 } | |
| 1029 | |
| 1030 static _length(int id) native "File_Length"; | |
| 1031 | |
| 1032 int lengthSync() { | |
| 1033 _checkNotClosed(); | |
| 1034 var result = _length(_id); | |
| 1035 if (result is OSError) { | |
| 1036 throw new FileIOException("length failed for file '$_name'", result); | |
| 1037 } | |
| 1038 return result; | |
| 1039 } | |
| 1040 | |
| 1041 Future<RandomAccessFile> flush() { | |
| 1042 _ensureFileService(); | |
| 1043 Completer<RandomAccessFile> completer = new Completer<RandomAccessFile>(); | |
| 1044 if (closed) return _completeWithClosedException(completer); | |
| 1045 List request = new List(2); | |
| 1046 request[0] = _FLUSH_REQUEST; | |
| 1047 request[1] = _id; | |
| 1048 return _fileService.call(request).transform((response) { | |
| 1049 if (_isErrorResponse(response)) { | |
| 1050 throw _exceptionFromResponse(response, | |
| 1051 "flush failed for file '$_name'"); | |
| 1052 } | |
| 1053 return this; | |
| 1054 }); | |
| 1055 } | |
| 1056 | |
| 1057 static _flush(int id) native "File_Flush"; | |
| 1058 | |
| 1059 void flushSync() { | |
| 1060 _checkNotClosed(); | |
| 1061 var result = _flush(_id); | |
| 1062 if (result is OSError) { | |
| 1063 throw new FileIOException("flush failed for file '$_name'", result); | |
| 1064 } | |
| 1065 } | |
| 1066 | |
| 1067 String get name => _name; | |
| 1068 | |
| 1069 void _ensureFileService() { | |
| 1070 if (_fileService == null) { | |
| 1071 _fileService = _newServicePort(); | |
| 1072 } | |
| 1073 } | |
| 1074 | |
| 1075 bool get closed => _id == 0; | |
| 1076 | |
| 1077 void _checkNotClosed() { | |
| 1078 if (closed) { | |
| 1079 throw new FileIOException("File closed '$_name'"); | |
| 1080 } | |
| 1081 } | |
| 1082 | |
| 1083 Future _completeWithClosedException(Completer completer) { | |
| 1084 new Timer(0, (t) { | |
| 1085 completer.completeException( | |
| 1086 new FileIOException("File closed '$_name'")); | |
| 1087 }); | |
| 1088 return completer.future; | |
| 1089 } | |
| 1090 | |
| 1091 final String _name; | |
| 1092 int _id; | |
| 1093 | |
| 1094 SendPort _fileService; | |
| 1095 } | |
| OLD | NEW |