OLD | NEW |
| (Empty) |
1 // Copyright (c) 2013, 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 part of dart.io; | |
6 | |
7 // Read the file in blocks of size 64k. | |
8 const int _BLOCK_SIZE = 64 * 1024; | |
9 | |
10 | |
11 class _FileStream extends Stream<List<int>> { | |
12 // Stream controller. | |
13 StreamController<List<int>> _controller; | |
14 | |
15 // Information about the underlying file. | |
16 String _path; | |
17 RandomAccessFile _openedFile; | |
18 int _position; | |
19 int _end; | |
20 final Completer _closeCompleter = new Completer(); | |
21 | |
22 // Has the stream been paused or unsubscribed? | |
23 bool _unsubscribed = false; | |
24 | |
25 // Is there a read currently in progress? | |
26 bool _readInProgress = true; | |
27 bool _closed = false; | |
28 | |
29 bool _atEnd = false; | |
30 | |
31 _FileStream(this._path, this._position, this._end) { | |
32 if (_position == null) _position = 0; | |
33 } | |
34 | |
35 _FileStream.forStdin() : _position = 0; | |
36 | |
37 StreamSubscription<List<int>> listen(void onData(List<int> event), | |
38 {Function onError, | |
39 void onDone(), | |
40 bool cancelOnError}) { | |
41 _setupController(); | |
42 return _controller.stream.listen(onData, | |
43 onError: onError, | |
44 onDone: onDone, | |
45 cancelOnError: cancelOnError); | |
46 } | |
47 | |
48 void _setupController() { | |
49 _controller = new StreamController<List<int>>(sync: true, | |
50 onListen: _start, | |
51 onResume: _readBlock, | |
52 onCancel: () { | |
53 _unsubscribed = true; | |
54 return _closeFile(); | |
55 }); | |
56 } | |
57 | |
58 Future _closeFile() { | |
59 if (_readInProgress || _closed) { | |
60 return _closeCompleter.future; | |
61 } | |
62 _closed = true; | |
63 | |
64 void done() { | |
65 _closeCompleter.complete(); | |
66 _controller.close(); | |
67 } | |
68 | |
69 _openedFile.close() | |
70 .catchError(_controller.addError) | |
71 .whenComplete(done); | |
72 return _closeCompleter.future; | |
73 } | |
74 | |
75 void _readBlock() { | |
76 // Don't start a new read if one is already in progress. | |
77 if (_readInProgress) return; | |
78 if (_atEnd) { | |
79 _closeFile(); | |
80 return; | |
81 } | |
82 _readInProgress = true; | |
83 int readBytes = _BLOCK_SIZE; | |
84 if (_end != null) { | |
85 readBytes = min(readBytes, _end - _position); | |
86 if (readBytes < 0) { | |
87 _readInProgress = false; | |
88 if (!_unsubscribed) { | |
89 _controller.addError(new RangeError("Bad end position: $_end")); | |
90 _closeFile(); | |
91 _unsubscribed = true; | |
92 } | |
93 return; | |
94 } | |
95 } | |
96 _openedFile.read(readBytes) | |
97 .then((block) { | |
98 _readInProgress = false; | |
99 if (_unsubscribed) { | |
100 _closeFile(); | |
101 return; | |
102 } | |
103 _position += block.length; | |
104 if (block.length < readBytes || | |
105 (_end != null && _position == _end)) { | |
106 _atEnd = true; | |
107 } | |
108 if (!_atEnd && !_controller.isPaused) { | |
109 _readBlock(); | |
110 } | |
111 _controller.add(block); | |
112 if (_atEnd) { | |
113 _closeFile(); | |
114 } | |
115 }) | |
116 .catchError((e, s) { | |
117 if (!_unsubscribed) { | |
118 _controller.addError(e, s); | |
119 _closeFile(); | |
120 _unsubscribed = true; | |
121 } | |
122 }); | |
123 } | |
124 | |
125 void _start() { | |
126 if (_position < 0) { | |
127 _controller.addError(new RangeError("Bad start position: $_position")); | |
128 _controller.close(); | |
129 _closeCompleter.complete(); | |
130 return; | |
131 } | |
132 | |
133 void onReady(RandomAccessFile file) { | |
134 _openedFile = file; | |
135 _readInProgress = false; | |
136 _readBlock(); | |
137 } | |
138 | |
139 void onOpenFile(RandomAccessFile file) { | |
140 if (_position > 0) { | |
141 file.setPosition(_position) | |
142 .then(onReady, onError: (e, s) { | |
143 _controller.addError(e, s); | |
144 _readInProgress = false; | |
145 _closeFile(); | |
146 }); | |
147 } else { | |
148 onReady(file); | |
149 } | |
150 } | |
151 | |
152 void openFailed(error, stackTrace) { | |
153 _controller.addError(error, stackTrace); | |
154 _controller.close(); | |
155 _closeCompleter.complete(); | |
156 } | |
157 | |
158 if (_path != null) { | |
159 new File(_path).open(mode: FileMode.READ) | |
160 .then(onOpenFile, onError: openFailed); | |
161 } else { | |
162 try { | |
163 onOpenFile(_File._openStdioSync(0)); | |
164 } catch (e, s) { | |
165 openFailed(e, s); | |
166 } | |
167 } | |
168 } | |
169 } | |
170 | |
171 class _FileStreamConsumer extends StreamConsumer<List<int>> { | |
172 File _file; | |
173 Future<RandomAccessFile> _openFuture; | |
174 | |
175 _FileStreamConsumer(File this._file, FileMode mode) { | |
176 _openFuture = _file.open(mode: mode); | |
177 } | |
178 | |
179 _FileStreamConsumer.fromStdio(int fd) { | |
180 assert(1 <= fd && fd <= 2); | |
181 _openFuture = new Future.value(_File._openStdioSync(fd)); | |
182 } | |
183 | |
184 Future<File> addStream(Stream<List<int>> stream) { | |
185 Completer<File> completer = new Completer<File>.sync(); | |
186 _openFuture | |
187 .then((openedFile) { | |
188 var _subscription; | |
189 void error(e, [StackTrace stackTrace]) { | |
190 _subscription.cancel(); | |
191 openedFile.close(); | |
192 completer.completeError(e, stackTrace); | |
193 } | |
194 _subscription = stream.listen( | |
195 (d) { | |
196 _subscription.pause(); | |
197 try { | |
198 openedFile.writeFrom(d, 0, d.length) | |
199 .then((_) => _subscription.resume(), | |
200 onError: error); | |
201 } catch (e, stackTrace) { | |
202 error(e, stackTrace); | |
203 } | |
204 }, | |
205 onDone: () { | |
206 completer.complete(_file); | |
207 }, | |
208 onError: error, | |
209 cancelOnError: true); | |
210 }) | |
211 .catchError(completer.completeError); | |
212 return completer.future; | |
213 } | |
214 | |
215 Future<File> close() => | |
216 _openFuture.then((openedFile) => openedFile.close()); | |
217 } | |
218 | |
219 | |
220 // Class for encapsulating the native implementation of files. | |
221 class _File extends FileSystemEntity implements File { | |
222 final String path; | |
223 | |
224 // Constructor for file. | |
225 _File(this.path) { | |
226 if (path is! String) { | |
227 throw new ArgumentError('${Error.safeToString(path)} ' | |
228 'is not a String'); | |
229 } | |
230 } | |
231 | |
232 Future<bool> exists() { | |
233 return _IOService._dispatch(_FILE_EXISTS, [path]).then((response) { | |
234 if (_isErrorResponse(response)) { | |
235 throw _exceptionFromResponse(response, "Cannot check existence", path); | |
236 } | |
237 return response; | |
238 }); | |
239 } | |
240 | |
241 external static _exists(String path); | |
242 | |
243 bool existsSync() { | |
244 var result = _exists(path); | |
245 throwIfError(result, "Cannot check existence of file", path); | |
246 return result; | |
247 } | |
248 | |
249 File get absolute => new File(_absolutePath); | |
250 | |
251 Future<FileStat> stat() => FileStat.stat(path); | |
252 | |
253 FileStat statSync() => FileStat.statSync(path); | |
254 | |
255 Future<File> create({bool recursive: false}) { | |
256 var result = recursive ? parent.create(recursive: true) | |
257 : new Future.value(null); | |
258 return result | |
259 .then((_) => _IOService._dispatch(_FILE_CREATE, [path])) | |
260 .then((response) { | |
261 if (_isErrorResponse(response)) { | |
262 throw _exceptionFromResponse(response, "Cannot create file", path); | |
263 } | |
264 return this; | |
265 }); | |
266 } | |
267 | |
268 external static _create(String path); | |
269 | |
270 external static _createLink(String path, String target); | |
271 | |
272 external static _linkTarget(String path); | |
273 | |
274 void createSync({bool recursive: false}) { | |
275 if (recursive) { | |
276 parent.createSync(recursive: true); | |
277 } | |
278 var result = _create(path); | |
279 throwIfError(result, "Cannot create file", path); | |
280 } | |
281 | |
282 Future<File> _delete({bool recursive: false}) { | |
283 if (recursive) { | |
284 return new Directory(path).delete(recursive: true).then((_) => this); | |
285 } | |
286 return _IOService._dispatch(_FILE_DELETE, [path]).then((response) { | |
287 if (_isErrorResponse(response)) { | |
288 throw _exceptionFromResponse(response, "Cannot delete file", path); | |
289 } | |
290 return this; | |
291 }); | |
292 } | |
293 | |
294 external static _deleteNative(String path); | |
295 | |
296 external static _deleteLinkNative(String path); | |
297 | |
298 void _deleteSync({bool recursive: false}) { | |
299 if (recursive) { | |
300 return new Directory(path).deleteSync(recursive: true); | |
301 } | |
302 var result = _deleteNative(path); | |
303 throwIfError(result, "Cannot delete file", path); | |
304 } | |
305 | |
306 Future<File> rename(String newPath) { | |
307 return _IOService._dispatch(_FILE_RENAME, [path, newPath]).then((response) { | |
308 if (_isErrorResponse(response)) { | |
309 throw _exceptionFromResponse( | |
310 response, "Cannot rename file to '$newPath'", path); | |
311 } | |
312 return new File(newPath); | |
313 }); | |
314 } | |
315 | |
316 external static _rename(String oldPath, String newPath); | |
317 | |
318 external static _renameLink(String oldPath, String newPath); | |
319 | |
320 File renameSync(String newPath) { | |
321 var result = _rename(path, newPath); | |
322 throwIfError(result, "Cannot rename file to '$newPath'", path); | |
323 return new File(newPath); | |
324 } | |
325 | |
326 Future<File> copy(String newPath) { | |
327 return _IOService._dispatch(_FILE_COPY, [path, newPath]).then((response) { | |
328 if (_isErrorResponse(response)) { | |
329 throw _exceptionFromResponse( | |
330 response, "Cannot copy file to '$newPath'", path); | |
331 } | |
332 return new File(newPath); | |
333 }); | |
334 } | |
335 | |
336 external static _copy(String oldPath, String newPath); | |
337 | |
338 File copySync(String newPath) { | |
339 var result = _copy(path, newPath); | |
340 throwIfError(result, "Cannot copy file to '$newPath'", path); | |
341 return new File(newPath); | |
342 } | |
343 | |
344 Future<RandomAccessFile> open({FileMode mode: FileMode.READ}) { | |
345 if (mode != FileMode.READ && | |
346 mode != FileMode.WRITE && | |
347 mode != FileMode.APPEND && | |
348 mode != FileMode.WRITE_ONLY && | |
349 mode != FileMode.WRITE_ONLY_APPEND) { | |
350 return new Future.error( | |
351 new ArgumentError('Invalid file mode for this operation')); | |
352 } | |
353 return _IOService._dispatch(_FILE_OPEN, [path, mode._mode]) | |
354 .then((response) { | |
355 if (_isErrorResponse(response)) { | |
356 throw _exceptionFromResponse(response, "Cannot open file", path); | |
357 } | |
358 return new _RandomAccessFile(response, path); | |
359 }); | |
360 } | |
361 | |
362 Future<int> length() { | |
363 return _IOService._dispatch(_FILE_LENGTH_FROM_PATH, [path]) | |
364 .then((response) { | |
365 if (_isErrorResponse(response)) { | |
366 throw _exceptionFromResponse(response, | |
367 "Cannot retrieve length of file", | |
368 path); | |
369 } | |
370 return response; | |
371 }); | |
372 } | |
373 | |
374 | |
375 external static _lengthFromPath(String path); | |
376 | |
377 int lengthSync() { | |
378 var result = _lengthFromPath(path); | |
379 throwIfError(result, "Cannot retrieve length of file", path); | |
380 return result; | |
381 } | |
382 | |
383 Future<DateTime> lastModified() { | |
384 return _IOService._dispatch(_FILE_LAST_MODIFIED, [path]).then((response) { | |
385 if (_isErrorResponse(response)) { | |
386 throw _exceptionFromResponse(response, | |
387 "Cannot retrieve modification time", | |
388 path); | |
389 } | |
390 return new DateTime.fromMillisecondsSinceEpoch(response); | |
391 }); | |
392 } | |
393 | |
394 external static _lastModified(String path); | |
395 | |
396 DateTime lastModifiedSync() { | |
397 var ms = _lastModified(path); | |
398 throwIfError(ms, "Cannot retrieve modification time", path); | |
399 return new DateTime.fromMillisecondsSinceEpoch(ms); | |
400 } | |
401 | |
402 external static _open(String path, int mode); | |
403 | |
404 RandomAccessFile openSync({FileMode mode: FileMode.READ}) { | |
405 if (mode != FileMode.READ && | |
406 mode != FileMode.WRITE && | |
407 mode != FileMode.APPEND && | |
408 mode != FileMode.WRITE_ONLY && | |
409 mode != FileMode.WRITE_ONLY_APPEND) { | |
410 throw new ArgumentError('Invalid file mode for this operation'); | |
411 } | |
412 var id = _open(path, mode._mode); | |
413 throwIfError(id, "Cannot open file", path); | |
414 return new _RandomAccessFile(id, path); | |
415 } | |
416 | |
417 external static int _openStdio(int fd); | |
418 | |
419 static RandomAccessFile _openStdioSync(int fd) { | |
420 var id = _openStdio(fd); | |
421 if (id == 0) { | |
422 throw new FileSystemException("Cannot open stdio file for: $fd"); | |
423 } | |
424 return new _RandomAccessFile(id, ""); | |
425 } | |
426 | |
427 Stream<List<int>> openRead([int start, int end]) { | |
428 return new _FileStream(path, start, end); | |
429 } | |
430 | |
431 IOSink openWrite({FileMode mode: FileMode.WRITE, | |
432 Encoding encoding: UTF8}) { | |
433 if (mode != FileMode.WRITE && | |
434 mode != FileMode.APPEND && | |
435 mode != FileMode.WRITE_ONLY && | |
436 mode != FileMode.WRITE_ONLY_APPEND) { | |
437 throw new ArgumentError('Invalid file mode for this operation'); | |
438 } | |
439 var consumer = new _FileStreamConsumer(this, mode); | |
440 return new IOSink(consumer, encoding: encoding); | |
441 } | |
442 | |
443 Future<List<int>> readAsBytes() { | |
444 Future<List<int>> readDataChunked(file) { | |
445 var builder = new BytesBuilder(copy: false); | |
446 var completer = new Completer(); | |
447 void read() { | |
448 file.read(_BLOCK_SIZE).then((data) { | |
449 if (data.length > 0) { | |
450 builder.add(data); | |
451 read(); | |
452 } else { | |
453 completer.complete(builder.takeBytes()); | |
454 } | |
455 }, onError: completer.completeError); | |
456 } | |
457 read(); | |
458 return completer.future; | |
459 } | |
460 | |
461 return open().then((file) { | |
462 return file.length().then((length) { | |
463 if (length == 0) { | |
464 // May be character device, try to read it in chunks. | |
465 return readDataChunked(file); | |
466 } | |
467 return file.read(length); | |
468 }).whenComplete(file.close); | |
469 }); | |
470 } | |
471 | |
472 List<int> readAsBytesSync() { | |
473 var opened = openSync(); | |
474 try { | |
475 var data; | |
476 var length = opened.lengthSync(); | |
477 if (length == 0) { | |
478 // May be character device, try to read it in chunks. | |
479 var builder = new BytesBuilder(copy: false); | |
480 do { | |
481 data = opened.readSync(_BLOCK_SIZE); | |
482 if (data.length > 0) builder.add(data); | |
483 } while (data.length > 0); | |
484 data = builder.takeBytes(); | |
485 } else { | |
486 data = opened.readSync(length); | |
487 } | |
488 return data; | |
489 } finally { | |
490 opened.closeSync(); | |
491 } | |
492 } | |
493 | |
494 String _tryDecode(List<int> bytes, Encoding encoding) { | |
495 try { | |
496 return encoding.decode(bytes); | |
497 } catch (_) { | |
498 throw new FileSystemException( | |
499 "Failed to decode data using encoding '${encoding.name}'", path); | |
500 } | |
501 } | |
502 | |
503 Future<String> readAsString({Encoding encoding: UTF8}) => | |
504 readAsBytes().then((bytes) => _tryDecode(bytes, encoding)); | |
505 | |
506 String readAsStringSync({Encoding encoding: UTF8}) => | |
507 _tryDecode(readAsBytesSync(), encoding); | |
508 | |
509 Future<List<String>> readAsLines({Encoding encoding: UTF8}) => | |
510 readAsString(encoding: encoding).then(const LineSplitter().convert); | |
511 | |
512 List<String> readAsLinesSync({Encoding encoding: UTF8}) => | |
513 const LineSplitter().convert(readAsStringSync(encoding: encoding)); | |
514 | |
515 Future<File> writeAsBytes(List<int> bytes, | |
516 {FileMode mode: FileMode.WRITE, | |
517 bool flush: false}) { | |
518 return open(mode: mode).then((file) { | |
519 return file.writeFrom(bytes, 0, bytes.length) | |
520 .then((_) { | |
521 if (flush) return file.flush().then((_) => this); | |
522 return this; | |
523 }) | |
524 .whenComplete(file.close); | |
525 }); | |
526 } | |
527 | |
528 void writeAsBytesSync(List<int> bytes, | |
529 {FileMode mode: FileMode.WRITE, | |
530 bool flush: false}) { | |
531 RandomAccessFile opened = openSync(mode: mode); | |
532 try { | |
533 opened.writeFromSync(bytes, 0, bytes.length); | |
534 if (flush) opened.flushSync(); | |
535 } finally { | |
536 opened.closeSync(); | |
537 } | |
538 } | |
539 | |
540 Future<File> writeAsString(String contents, | |
541 {FileMode mode: FileMode.WRITE, | |
542 Encoding encoding: UTF8, | |
543 bool flush: false}) { | |
544 try { | |
545 return writeAsBytes(encoding.encode(contents), mode: mode, flush: flush); | |
546 } catch (e) { | |
547 return new Future.error(e); | |
548 } | |
549 } | |
550 | |
551 void writeAsStringSync(String contents, | |
552 {FileMode mode: FileMode.WRITE, | |
553 Encoding encoding: UTF8, | |
554 bool flush: false}) { | |
555 writeAsBytesSync(encoding.encode(contents), mode: mode, flush: flush); | |
556 } | |
557 | |
558 String toString() => "File: '$path'"; | |
559 | |
560 static throwIfError(Object result, String msg, String path) { | |
561 if (result is OSError) { | |
562 throw new FileSystemException(msg, path, result); | |
563 } | |
564 } | |
565 } | |
566 | |
567 abstract class _RandomAccessFileOps { | |
568 external factory _RandomAccessFileOps(int pointer); | |
569 | |
570 int getPointer(); | |
571 int close(); | |
572 readByte(); | |
573 read(int bytes); | |
574 readInto(List<int> buffer, int start, int end); | |
575 writeByte(int value); | |
576 writeFrom(List<int> buffer, int start, int end); | |
577 position(); | |
578 setPosition(int position); | |
579 truncate(int length); | |
580 length(); | |
581 flush(); | |
582 lock(int lock, int start, int end); | |
583 } | |
584 | |
585 class _RandomAccessFile implements RandomAccessFile { | |
586 static bool _connectedResourceHandler = false; | |
587 | |
588 final String path; | |
589 | |
590 bool _asyncDispatched = false; | |
591 SendPort _fileService; | |
592 | |
593 _FileResourceInfo _resourceInfo; | |
594 _RandomAccessFileOps _ops; | |
595 | |
596 _RandomAccessFile(int pointer, this.path) { | |
597 _ops = new _RandomAccessFileOps(pointer); | |
598 _resourceInfo = new _FileResourceInfo(this); | |
599 _maybeConnectHandler(); | |
600 } | |
601 | |
602 void _maybePerformCleanup() { | |
603 if (closed) { | |
604 _FileResourceInfo.FileClosed(_resourceInfo); | |
605 } | |
606 } | |
607 | |
608 _maybeConnectHandler() { | |
609 if (!_connectedResourceHandler) { | |
610 // TODO(ricow): We probably need to set these in some initialization code. | |
611 // We need to make sure that these are always available from the | |
612 // observatory even if no files (or sockets for the socket ones) are | |
613 // open. | |
614 registerExtension('ext.dart.io.getOpenFiles', | |
615 _FileResourceInfo.getOpenFiles); | |
616 registerExtension('ext.dart.io.getFileByID', | |
617 _FileResourceInfo.getFileInfoMapByID); | |
618 _connectedResourceHandler = true; | |
619 } | |
620 } | |
621 | |
622 Future<RandomAccessFile> close() { | |
623 return _dispatch(_FILE_CLOSE, [null], markClosed: true).then((result) { | |
624 if (result != -1) { | |
625 closed = closed || (result == 0); | |
626 _maybePerformCleanup(); | |
627 return this; | |
628 } else { | |
629 throw new FileSystemException("Cannot close file", path); | |
630 } | |
631 }); | |
632 } | |
633 | |
634 void closeSync() { | |
635 _checkAvailable(); | |
636 var id = _ops.close(); | |
637 if (id == -1) { | |
638 throw new FileSystemException("Cannot close file", path); | |
639 } | |
640 closed = closed || (id == 0); | |
641 _maybePerformCleanup(); | |
642 } | |
643 | |
644 Future<int> readByte() { | |
645 return _dispatch(_FILE_READ_BYTE, [null]).then((response) { | |
646 if (_isErrorResponse(response)) { | |
647 throw _exceptionFromResponse(response, "readByte failed", path); | |
648 } | |
649 _resourceInfo.addRead(1); | |
650 return response; | |
651 }); | |
652 } | |
653 | |
654 int readByteSync() { | |
655 _checkAvailable(); | |
656 var result = _ops.readByte(); | |
657 if (result is OSError) { | |
658 throw new FileSystemException("readByte failed", path, result); | |
659 } | |
660 _resourceInfo.addRead(1); | |
661 return result; | |
662 } | |
663 | |
664 Future<List<int>> read(int bytes) { | |
665 if (bytes is !int) { | |
666 throw new ArgumentError(bytes); | |
667 } | |
668 return _dispatch(_FILE_READ, [null, bytes]).then((response) { | |
669 if (_isErrorResponse(response)) { | |
670 throw _exceptionFromResponse(response, "read failed", path); | |
671 } | |
672 _resourceInfo.addRead(response[1].length); | |
673 return response[1]; | |
674 }); | |
675 } | |
676 | |
677 List<int> readSync(int bytes) { | |
678 _checkAvailable(); | |
679 if (bytes is !int) { | |
680 throw new ArgumentError(bytes); | |
681 } | |
682 var result = _ops.read(bytes); | |
683 if (result is OSError) { | |
684 throw new FileSystemException("readSync failed", path, result); | |
685 } | |
686 _resourceInfo.addRead(result.length); | |
687 return result; | |
688 } | |
689 | |
690 Future<int> readInto(List<int> buffer, [int start = 0, int end]) { | |
691 if ((buffer is !List) || | |
692 ((start != null) && (start is !int)) || | |
693 ((end != null) && (end is !int))) { | |
694 throw new ArgumentError(); | |
695 } | |
696 end = RangeError.checkValidRange(start, end, buffer.length); | |
697 if (end == start) { | |
698 return new Future.value(0); | |
699 } | |
700 int length = end - start; | |
701 return _dispatch(_FILE_READ_INTO, [null, length]).then((response) { | |
702 if (_isErrorResponse(response)) { | |
703 throw _exceptionFromResponse(response, "readInto failed", path); | |
704 } | |
705 var read = response[1]; | |
706 var data = response[2]; | |
707 buffer.setRange(start, start + read, data); | |
708 _resourceInfo.addRead(read); | |
709 return read; | |
710 }); | |
711 } | |
712 | |
713 int readIntoSync(List<int> buffer, [int start = 0, int end]) { | |
714 _checkAvailable(); | |
715 if ((buffer is !List) || | |
716 ((start != null) && (start is !int)) || | |
717 ((end != null) && (end is !int))) { | |
718 throw new ArgumentError(); | |
719 } | |
720 end = RangeError.checkValidRange(start, end, buffer.length); | |
721 if (end == start) { | |
722 return 0; | |
723 } | |
724 var result = _ops.readInto(buffer, start, end); | |
725 if (result is OSError) { | |
726 throw new FileSystemException("readInto failed", path, result); | |
727 } | |
728 _resourceInfo.addRead(result); | |
729 return result; | |
730 } | |
731 | |
732 Future<RandomAccessFile> writeByte(int value) { | |
733 if (value is !int) { | |
734 throw new ArgumentError(value); | |
735 } | |
736 return _dispatch(_FILE_WRITE_BYTE, [null, value]).then((response) { | |
737 if (_isErrorResponse(response)) { | |
738 throw _exceptionFromResponse(response, "writeByte failed", path); | |
739 } | |
740 _resourceInfo.addWrite(1); | |
741 return this; | |
742 }); | |
743 } | |
744 | |
745 int writeByteSync(int value) { | |
746 _checkAvailable(); | |
747 if (value is !int) { | |
748 throw new ArgumentError(value); | |
749 } | |
750 var result = _ops.writeByte(value); | |
751 if (result is OSError) { | |
752 throw new FileSystemException("writeByte failed", path, result); | |
753 } | |
754 _resourceInfo.addWrite(1); | |
755 return result; | |
756 } | |
757 | |
758 Future<RandomAccessFile> writeFrom( | |
759 List<int> buffer, [int start = 0, int end]) { | |
760 if ((buffer is !List) || | |
761 ((start != null) && (start is !int)) || | |
762 ((end != null) && (end is !int))) { | |
763 throw new ArgumentError("Invalid arguments to writeFrom"); | |
764 } | |
765 end = RangeError.checkValidRange(start, end, buffer.length); | |
766 if (end == start) { | |
767 return new Future.value(this); | |
768 } | |
769 _BufferAndStart result; | |
770 try { | |
771 result = _ensureFastAndSerializableByteData(buffer, start, end); | |
772 } catch (e) { | |
773 return new Future.error(e); | |
774 } | |
775 | |
776 List request = new List(4); | |
777 request[0] = null; | |
778 request[1] = result.buffer; | |
779 request[2] = result.start; | |
780 request[3] = end - (start - result.start); | |
781 return _dispatch(_FILE_WRITE_FROM, request).then((response) { | |
782 if (_isErrorResponse(response)) { | |
783 throw _exceptionFromResponse(response, "writeFrom failed", path); | |
784 } | |
785 _resourceInfo.addWrite(end - (start - result.start)); | |
786 return this; | |
787 }); | |
788 } | |
789 | |
790 void writeFromSync(List<int> buffer, [int start = 0, int end]) { | |
791 _checkAvailable(); | |
792 if ((buffer is !List) || | |
793 ((start != null) && (start is !int)) || | |
794 ((end != null) && (end is !int))) { | |
795 throw new ArgumentError("Invalid arguments to writeFromSync"); | |
796 } | |
797 end = RangeError.checkValidRange(start, end, buffer.length); | |
798 if (end == start) { | |
799 return; | |
800 } | |
801 _BufferAndStart bufferAndStart = | |
802 _ensureFastAndSerializableByteData(buffer, start, end); | |
803 var result = _ops.writeFrom(bufferAndStart.buffer, | |
804 bufferAndStart.start, | |
805 end - (start - bufferAndStart.start)); | |
806 if (result is OSError) { | |
807 throw new FileSystemException("writeFrom failed", path, result); | |
808 } | |
809 _resourceInfo.addWrite(end - (start - bufferAndStart.start)); | |
810 } | |
811 | |
812 Future<RandomAccessFile> writeString(String string, | |
813 {Encoding encoding: UTF8}) { | |
814 if (encoding is! Encoding) { | |
815 throw new ArgumentError(encoding); | |
816 } | |
817 var data = encoding.encode(string); | |
818 return writeFrom(data, 0, data.length); | |
819 } | |
820 | |
821 void writeStringSync(String string, {Encoding encoding: UTF8}) { | |
822 if (encoding is! Encoding) { | |
823 throw new ArgumentError(encoding); | |
824 } | |
825 var data = encoding.encode(string); | |
826 writeFromSync(data, 0, data.length); | |
827 } | |
828 | |
829 Future<int> position() { | |
830 return _dispatch(_FILE_POSITION, [null]).then((response) { | |
831 if (_isErrorResponse(response)) { | |
832 throw _exceptionFromResponse(response, "position failed", path); | |
833 } | |
834 return response; | |
835 }); | |
836 } | |
837 | |
838 int positionSync() { | |
839 _checkAvailable(); | |
840 var result = _ops.position(); | |
841 if (result is OSError) { | |
842 throw new FileSystemException("position failed", path, result); | |
843 } | |
844 return result; | |
845 } | |
846 | |
847 Future<RandomAccessFile> setPosition(int position) { | |
848 return _dispatch(_FILE_SET_POSITION, [null, position]) | |
849 .then((response) { | |
850 if (_isErrorResponse(response)) { | |
851 throw _exceptionFromResponse(response, "setPosition failed", path); | |
852 } | |
853 return this; | |
854 }); | |
855 } | |
856 | |
857 void setPositionSync(int position) { | |
858 _checkAvailable(); | |
859 var result = _ops.setPosition(position); | |
860 if (result is OSError) { | |
861 throw new FileSystemException("setPosition failed", path, result); | |
862 } | |
863 } | |
864 | |
865 Future<RandomAccessFile> truncate(int length) { | |
866 return _dispatch(_FILE_TRUNCATE, [null, length]).then((response) { | |
867 if (_isErrorResponse(response)) { | |
868 throw _exceptionFromResponse(response, "truncate failed", path); | |
869 } | |
870 return this; | |
871 }); | |
872 } | |
873 | |
874 void truncateSync(int length) { | |
875 _checkAvailable(); | |
876 var result = _ops.truncate(length); | |
877 if (result is OSError) { | |
878 throw new FileSystemException("truncate failed", path, result); | |
879 } | |
880 } | |
881 | |
882 Future<int> length() { | |
883 return _dispatch(_FILE_LENGTH, [null]).then((response) { | |
884 if (_isErrorResponse(response)) { | |
885 throw _exceptionFromResponse(response, "length failed", path); | |
886 } | |
887 return response; | |
888 }); | |
889 } | |
890 | |
891 int lengthSync() { | |
892 _checkAvailable(); | |
893 var result = _ops.length(); | |
894 if (result is OSError) { | |
895 throw new FileSystemException("length failed", path, result); | |
896 } | |
897 return result; | |
898 } | |
899 | |
900 Future<RandomAccessFile> flush() { | |
901 return _dispatch(_FILE_FLUSH, [null]).then((response) { | |
902 if (_isErrorResponse(response)) { | |
903 throw _exceptionFromResponse(response, | |
904 "flush failed", | |
905 path); | |
906 } | |
907 return this; | |
908 }); | |
909 } | |
910 | |
911 void flushSync() { | |
912 _checkAvailable(); | |
913 var result = _ops.flush(); | |
914 if (result is OSError) { | |
915 throw new FileSystemException("flush failed", path, result); | |
916 } | |
917 } | |
918 | |
919 static final int LOCK_UNLOCK = 0; | |
920 static final int LOCK_SHARED = 1; | |
921 static final int LOCK_EXCLUSIVE = 2; | |
922 | |
923 Future<RandomAccessFile> lock( | |
924 [FileLock mode = FileLock.EXCLUSIVE, int start = 0, int end = -1]) { | |
925 if ((mode is !FileLock) || (start is !int) || (end is !int)) { | |
926 throw new ArgumentError(); | |
927 } | |
928 if ((start < 0) || (end < -1) || ((end != -1) && (start >= end))) { | |
929 throw new ArgumentError(); | |
930 } | |
931 int lock = (mode == FileLock.EXCLUSIVE) ? LOCK_EXCLUSIVE : LOCK_SHARED; | |
932 return _dispatch(_FILE_LOCK, [null, lock, start, end]) | |
933 .then((response) { | |
934 if (_isErrorResponse(response)) { | |
935 throw _exceptionFromResponse(response, 'lock failed', path); | |
936 } | |
937 return this; | |
938 }); | |
939 } | |
940 | |
941 Future<RandomAccessFile> unlock([int start = 0, int end = -1]) { | |
942 if ((start is !int) || (end is !int)) { | |
943 throw new ArgumentError(); | |
944 } | |
945 if (start == end) { | |
946 throw new ArgumentError(); | |
947 } | |
948 return _dispatch(_FILE_LOCK, [null, LOCK_UNLOCK, start, end]) | |
949 .then((response) { | |
950 if (_isErrorResponse(response)) { | |
951 throw _exceptionFromResponse(response, 'unlock failed', path); | |
952 } | |
953 return this; | |
954 }); | |
955 } | |
956 | |
957 void lockSync( | |
958 [FileLock mode = FileLock.EXCLUSIVE, int start = 0, int end = -1]) { | |
959 _checkAvailable(); | |
960 if ((mode is !FileLock) || (start is !int) || (end is !int)) { | |
961 throw new ArgumentError(); | |
962 } | |
963 if ((start < 0) || (end < -1) || ((end != -1) && (start >= end))) { | |
964 throw new ArgumentError(); | |
965 } | |
966 int lock = (mode == FileLock.EXCLUSIVE) ? LOCK_EXCLUSIVE : LOCK_SHARED; | |
967 var result = _ops.lock(lock, start, end); | |
968 if (result is OSError) { | |
969 throw new FileSystemException('lock failed', path, result); | |
970 } | |
971 } | |
972 | |
973 void unlockSync([int start = 0, int end = -1]) { | |
974 _checkAvailable(); | |
975 if ((start is !int) || (end is !int)) { | |
976 throw new ArgumentError(); | |
977 } | |
978 if (start == end) { | |
979 throw new ArgumentError(); | |
980 } | |
981 var result = _ops.lock(LOCK_UNLOCK, start, end); | |
982 if (result is OSError) { | |
983 throw new FileSystemException('unlock failed', path, result); | |
984 } | |
985 } | |
986 | |
987 bool closed = false; | |
988 | |
989 // Calling this function will increase the reference count on the native | |
990 // object that implements the file operations. It should only be called to | |
991 // pass the pointer to the IO Service, which will decrement the reference | |
992 // count when it is finished with it. | |
993 int _pointer() => _ops.getPointer(); | |
994 | |
995 Future _dispatch(int request, List data, { bool markClosed: false }) { | |
996 if (closed) { | |
997 return new Future.error(new FileSystemException("File closed", path)); | |
998 } | |
999 if (_asyncDispatched) { | |
1000 var msg = "An async operation is currently pending"; | |
1001 return new Future.error(new FileSystemException(msg, path)); | |
1002 } | |
1003 if (markClosed) { | |
1004 // Set closed to true to ensure that no more async requests can be issued | |
1005 // for this file. | |
1006 closed = true; | |
1007 } | |
1008 _asyncDispatched = true; | |
1009 data[0] = _pointer(); | |
1010 return _IOService._dispatch(request, data) | |
1011 .whenComplete(() { | |
1012 _asyncDispatched = false; | |
1013 }); | |
1014 } | |
1015 | |
1016 void _checkAvailable() { | |
1017 if (_asyncDispatched) { | |
1018 throw new FileSystemException("An async operation is currently pending", | |
1019 path); | |
1020 } | |
1021 if (closed) { | |
1022 throw new FileSystemException("File closed", path); | |
1023 } | |
1024 } | |
1025 } | |
OLD | NEW |