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 #library("input_stream"); |
| 6 |
| 7 #import("entry.dart"); |
| 8 #import("read_request.dart"); |
| 9 #import("utils.dart"); |
| 10 |
| 11 // TODO(nweiz): ensure that the C struct associated with an input stream gets |
| 12 // freed if the stream gets garbage collected before it's closed. |
| 13 /** |
| 14 * A stream of [ArchiveEntry]s being read from an archive. |
| 15 * |
| 16 * This is accessible via [ArchiveReader]. |
| 17 */ |
| 18 class ArchiveInputStream { |
| 19 /** |
| 20 * The id of the underlying archive. |
| 21 * |
| 22 * This will be set to null once the input stream has finished reading from |
| 23 * the archive. |
| 24 */ |
| 25 int _id; |
| 26 |
| 27 /** A [Completer] that will fire once the [_onEntry] callback is set. */ |
| 28 final Completer<Function> _onEntryCompleter; |
| 29 |
| 30 /** The callback to call when the input stream is closed. */ |
| 31 Function _onClosed; |
| 32 |
| 33 /** The callback to call when an error occurs. */ |
| 34 Function _onError; |
| 35 |
| 36 /** The entry that is currently eligible to read data from the archive. */ |
| 37 ArchiveEntry _currentEntry; |
| 38 |
| 39 ArchiveInputStream(this._id) : _onEntryCompleter = new Completer<Function>() { |
| 40 var future = _consumeHeaders(); |
| 41 future.handleException((e) { |
| 42 if (_onError != null) { |
| 43 _onError(e, future.stackTrace); |
| 44 return true; |
| 45 } else { |
| 46 throw e; |
| 47 } |
| 48 }); |
| 49 |
| 50 future.then((_) { |
| 51 close(); |
| 52 if (_onClosed != null) _onClosed(); |
| 53 }); |
| 54 } |
| 55 |
| 56 /** Whether this stream has finished reading entries. */ |
| 57 bool get closed() => _id == null; |
| 58 |
| 59 /** |
| 60 * Sets a callback to call when a new entry is read from the archive. |
| 61 * |
| 62 * The [ArchiveEntry] that's read from an archive initially only contains |
| 63 * header information such as the filename and permissions. To get the actual |
| 64 * data contained in the entry, use [ArchiveEntry.openInputStream]. |
| 65 * |
| 66 * Since the entries are read in sequence from the archive, the data stream |
| 67 * for one entry must be opened before the next entry is read from the |
| 68 * archive. The next entry will not be read until the return value of |
| 69 * [callback] has completed. |
| 70 * |
| 71 * If [callback] calls [ArchiveEntry.openInputStream] before it returns, or if |
| 72 * it doesn't want to read the contents of [entry], it can return null. |
| 73 */ |
| 74 void set onEntry(Future callback(ArchiveEntry entry)) { |
| 75 _onEntryCompleter.complete(callback); |
| 76 } |
| 77 |
| 78 /** |
| 79 * Sets a callback to call when the input stream is done emitting entries. |
| 80 */ |
| 81 void set onClosed(void callback()) { |
| 82 _onClosed = callback; |
| 83 } |
| 84 |
| 85 /** |
| 86 * Sets a callback to call if an error occurs while extracting the archive. |
| 87 * |
| 88 * [e] is the error that occured and [stack] is the stack trace of the error. |
| 89 */ |
| 90 void set onError(void callback(e, stack)) { |
| 91 _onError = callback; |
| 92 } |
| 93 |
| 94 /** |
| 95 * Closes the input stream. No more entries will be emitted. |
| 96 */ |
| 97 void close() { |
| 98 if (closed) return; |
| 99 call(FREE, _id).then((_) {}); |
| 100 _id = null; |
| 101 if (_currentEntry != null) _currentEntry.close(); |
| 102 if (!_onEntryCompleter.future.isComplete) _onEntryCompleter.complete(null); |
| 103 } |
| 104 |
| 105 /** |
| 106 * Consumes and emits all [ArchiveEntries] in this archive. |
| 107 */ |
| 108 Future _consumeHeaders() { |
| 109 if (closed) return new Future.immediate(null); |
| 110 var data; |
| 111 return call(NEXT_HEADER, _id).chain((_data) { |
| 112 data = _data; |
| 113 if (data == null) return new Future.immediate(null); |
| 114 return _emit(new ArchiveEntry(_id, data)). |
| 115 chain((_) => _consumeHeaders()); |
| 116 }); |
| 117 } |
| 118 |
| 119 /** |
| 120 * Emits [entry] to the [onEntry] callback. Returns a [Future] that will |
| 121 * complete once the callback's return value completes and the entry's data |
| 122 * has been fully consumed. |
| 123 */ |
| 124 Future _emit(ArchiveEntry entry) { |
| 125 _currentEntry = entry; |
| 126 var future = _onEntryCompleter.future.chain((onEntry) { |
| 127 if (closed) return new Future.immediate(null); |
| 128 var result = onEntry(entry); |
| 129 if (result is Future) return result; |
| 130 return new Future.immediate(null); |
| 131 }).chain((_) { |
| 132 if (entry.isInputOpen) return entry.inputComplete; |
| 133 return new Future.immediate(null); |
| 134 }); |
| 135 future.onComplete((_) { |
| 136 _currentEntry = null; |
| 137 entry.close(); |
| 138 }); |
| 139 return future; |
| 140 } |
| 141 } |
OLD | NEW |