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 |