114137d29a
Review URL: https://chromiumcodereview.appspot.com//10830191 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@10325 260f80e4-7a28-3924-810f-c04153c831b5
164 lines
4.9 KiB
Dart
164 lines
4.9 KiB
Dart
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
#library("input_stream");
|
|
|
|
#import("archive.dart", prefix: "archive");
|
|
#import("entry.dart");
|
|
#import("read_request.dart");
|
|
#import("utils.dart");
|
|
|
|
/**
|
|
* A stream of [ArchiveEntry]s being read from an archive.
|
|
*
|
|
* This is accessible via [ArchiveReader].
|
|
*/
|
|
class ArchiveInputStream {
|
|
/**
|
|
* The id of the underlying archive.
|
|
*
|
|
* This will be set to null once the input stream has finished reading from
|
|
* the archive.
|
|
*/
|
|
final Reference<int> _id;
|
|
|
|
/** A [Completer] that will fire once the [_onEntry] callback is set. */
|
|
final Completer<Function> _onEntryCompleter;
|
|
|
|
/** The callback to call when the input stream is closed. */
|
|
Function _onClosed;
|
|
|
|
/** The callback to call when an error occurs. */
|
|
Function _onError;
|
|
|
|
/** The entry that is currently eligible to read data from the archive. */
|
|
ArchiveEntry _currentEntry;
|
|
|
|
ArchiveInputStream(int id)
|
|
: _id = new Reference<int>(id),
|
|
_onEntryCompleter = new Completer<Function>() {
|
|
var future = _consumeHeaders();
|
|
future.handleException((e) {
|
|
if (_onError != null) {
|
|
_onError(e, future.stackTrace);
|
|
return true;
|
|
} else {
|
|
throw e;
|
|
}
|
|
});
|
|
|
|
future.then((_) {
|
|
close();
|
|
if (_onClosed != null) _onClosed();
|
|
});
|
|
|
|
attachFinalizer(this, (id) {
|
|
if (id.value != null) call(FREE, id.value).then(() {});
|
|
}, _id);
|
|
}
|
|
|
|
/** Whether this stream has finished reading entries. */
|
|
bool get closed() => _id.value == null;
|
|
|
|
/**
|
|
* Reads the entire contents of the archive at once.
|
|
*
|
|
* Note that this is mutually exclusive with reading individual entries using
|
|
* [onEntry].
|
|
*/
|
|
Future<List<CompleteArchiveEntry>> readAll() {
|
|
var completer = new Completer<List<Future<CompleteArchiveEntry>>>();
|
|
var result = <Future<CompleteArchiveEntry>>[];
|
|
|
|
this.onEntry = (entry) => result.add(entry.readAll());
|
|
this.onError = (e, stack) => completer.completeException(e, stack);
|
|
this.onClosed = () => completer.complete(result);
|
|
|
|
return completer.future.chain(Futures.wait);
|
|
}
|
|
|
|
/**
|
|
* Sets a callback to call when a new entry is read from the archive.
|
|
*
|
|
* The [ArchiveEntry] that's read from an archive initially only contains
|
|
* header information such as the filename and permissions. To get the actual
|
|
* data contained in the entry, use [ArchiveEntry.openInputStream].
|
|
*
|
|
* Since the entries are read in sequence from the archive, the data stream
|
|
* for one entry must be opened before the next entry is read from the
|
|
* archive. The next entry will not be read until the return value of
|
|
* [callback] has completed.
|
|
*
|
|
* If [callback] calls [ArchiveEntry.openInputStream] before it returns, or if
|
|
* it doesn't want to read the contents of [entry], it can return null.
|
|
*/
|
|
void set onEntry(Future callback(ArchiveEntry entry)) {
|
|
_onEntryCompleter.complete(callback);
|
|
}
|
|
|
|
/**
|
|
* Sets a callback to call when the input stream is done emitting entries.
|
|
*/
|
|
void set onClosed(void callback()) {
|
|
_onClosed = callback;
|
|
}
|
|
|
|
/**
|
|
* Sets a callback to call if an error occurs while extracting the archive.
|
|
*
|
|
* [e] is the error that occured and [stack] is the stack trace of the error.
|
|
*/
|
|
void set onError(void callback(e, stack)) {
|
|
_onError = callback;
|
|
}
|
|
|
|
/**
|
|
* Closes the input stream. No more entries will be emitted.
|
|
*/
|
|
void close() {
|
|
if (closed) return;
|
|
call(FREE, _id.value).then((_) {});
|
|
_id.value = null;
|
|
if (_currentEntry != null) _currentEntry.close();
|
|
if (!_onEntryCompleter.future.isComplete) _onEntryCompleter.complete(null);
|
|
}
|
|
|
|
/**
|
|
* Consumes and emits all [ArchiveEntries] in this archive.
|
|
*/
|
|
Future _consumeHeaders() {
|
|
if (closed) return new Future.immediate(null);
|
|
var data;
|
|
return call(NEXT_HEADER, _id.value).chain((_data) {
|
|
data = _data;
|
|
if (data == null) return new Future.immediate(null);
|
|
return _emit(new archive.ArchiveEntry.internal(data, _id.value)).
|
|
chain((_) => _consumeHeaders());
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Emits [entry] to the [onEntry] callback. Returns a [Future] that will
|
|
* complete once the callback's return value completes and the entry's data
|
|
* has been fully consumed.
|
|
*/
|
|
Future _emit(ArchiveEntry entry) {
|
|
_currentEntry = entry;
|
|
var future = _onEntryCompleter.future.chain((onEntry) {
|
|
if (closed) return new Future.immediate(null);
|
|
var result = onEntry(entry);
|
|
if (result is Future) return result;
|
|
return new Future.immediate(null);
|
|
}).chain((_) {
|
|
if (entry.isInputOpen) return entry.inputComplete;
|
|
return new Future.immediate(null);
|
|
});
|
|
future.onComplete((_) {
|
|
_currentEntry = null;
|
|
entry.close();
|
|
});
|
|
return future;
|
|
}
|
|
}
|