blob: fb3fbe49f9664746591ef4060968d08152838a44 [file] [log] [blame]
// Copyright (c) 2013, 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.
part of dart.io;
/**
* An instance of the default implementation of the [ZLibCodec].
*/
const ZLIB = const ZLibCodec();
/**
* The [ZLibCodec] encodes raw bytes to ZLib compressed bytes and decodes ZLib
* compressed bytes to raw bytes.
*/
class ZLibCodec extends Codec<List<int>, List<int>> {
/**
* The compression level of the [ZLibCodec].
*/
final int level;
/**
* Get a [Converter] for encoding to `ZLib` compressed data.
*/
Converter<List<int>, List<int>> get encoder =>
new ZLibEncoder(gzip: false, level: level);
/**
* Get a [Converter] for decoding `ZLib` compressed data.
*/
Converter<List<int>, List<int>> get decoder => const ZLibDecoder();
/**
* The compression-[level] can be set in the range of `1..10`, with `6` being
* the default compression level. Levels above 6 will have higher compression
* rates at the cost of more CPU and memory usage. Levels below 6 will use
* less CPU and memory, but at the cost of lower compression rates.
*/
const ZLibCodec({this.level: 6});
}
/**
* An instance of the default implementation of the [GZipCodec].
*/
const GZIP = const GZipCodec();
/**
* The [GZipCodec] encodes raw bytes to GZip compressed bytes and decodes GZip
* compressed bytes to raw bytes.
*
* The difference between [ZLibCodec] and [GZipCodec] is that the [GZipCodec]
* wraps the `ZLib` compressed bytes in `GZip` frames.
*/
class GZipCodec extends Codec<List<int>, List<int>> {
/**
* The compression level of the [ZLibCodec].
*/
final int level;
/**
* Get a [Converter] for encoding to `GZip` compressed data.
*/
Converter<List<int>, List<int>> get encoder =>
new ZLibEncoder(gzip: true, level: level);
/**
* Get a [Converter] for decoding `GZip` compressed data.
*/
Converter<List<int>, List<int>> get decoder => const ZLibDecoder();
/**
* The compression-[level] can be set in the range of `1..10`, with `6` being
* the default compression level. Levels above 6 will have higher compression
* rates at the cost of more CPU and memory usage. Levels below 6 will use
* less CPU and memory, but at the cost of lower compression rates.
*/
const GZipCodec({this.level: 6});
}
/**
* The [ZLibEncoder] is the encoder used by [ZLibCodec] and [GZipCodec] to
* compress data.
*/
class ZLibEncoder extends Converter<List<int>, List<int>> {
/**
* If [gzip] is true, `GZip` frames will be added to the compressed data.
*/
final bool gzip;
/**
* The compression level used by the encoder.
*/
final int level;
/**
* Create a new [ZLibEncoder] converter. If the [gzip] flag is set, the
* encoder will wrap the encoded ZLib data in GZip frames.
*/
const ZLibEncoder({this.gzip: false, this.level: 6});
/**
* Convert a list of bytes using the options given to the [ZLibEncoder]
* constructor.
*/
List<int> convert(List<int> bytes) {
_BufferSink sink = new _BufferSink();
startChunkedConversion(sink)
..add(bytes)
..close();
return sink.builder.takeBytes();
}
/**
* Start a chunked conversion using the options given to the [ZLibEncoder]
* constructor. While it accepts any [ChunkedConversionSink] taking
* [List<int>]'s, the optimal sink to be passed as [sink] is a
* [ByteConversionSink].
*/
ByteConversionSink startChunkedConversion(
ChunkedConversionSink<List<int>> sink) {
if (sink is! ByteConversionSink) {
sink = new ByteConversionSink.from(sink);
}
return new _ZLibEncoderSink(sink, gzip, level);
}
}
/**
* The [ZLibDecoder] is the decoder used by [ZLibCodec] and [GZipCodec] to
* decompress data.
*/
class ZLibDecoder extends Converter<List<int>, List<int>> {
/**
* Create a new [ZLibEncoder] converter.
*/
const ZLibDecoder();
/**
* Convert a list of bytes using the options given to the [ZLibDecoder]
* constructor.
*/
List<int> convert(List<int> bytes) {
_BufferSink sink = new _BufferSink();
startChunkedConversion(sink)
..add(bytes)
..close();
return sink.builder.takeBytes();
}
/**
* Start a chunked conversion. While it accepts any [ChunkedConversionSink]
* taking [List<int>]'s, the optimal sink to be passed as [sink] is a
* [ByteConversionSink].
*/
ByteConversionSink startChunkedConversion(
ChunkedConversionSink<List<int>> sink) {
if (sink is! ByteConversionSink) {
sink = new ByteConversionSink.from(sink);
}
return new _ZLibDecoderSink(sink);
}
}
class _BufferSink extends ByteConversionSink {
final BytesBuilder builder = new BytesBuilder();
void add(List<int> chunk) {
builder.add(chunk);
}
void addSlice(List<int> chunk, int start, int end, bool isLast) {
if (chunk is Uint8List) {
Uint8List list = chunk;
builder.add(new Uint8List.view(list.buffer, start, end - start));
} else {
builder.add(chunk.sublist(start, end));
}
}
void close() {}
}
class _ZLibEncoderSink extends _FilterSink {
_ZLibEncoderSink(ByteConversionSink sink, bool gzip, int level)
: super(sink, _Filter.newZLibDeflateFilter(gzip, level));
}
class _ZLibDecoderSink extends _FilterSink {
_ZLibDecoderSink(ByteConversionSink sink)
: super(sink, _Filter.newZLibInflateFilter());
}
class _FilterSink extends ByteConversionSink {
final _Filter _filter;
final ByteConversionSink _sink;
bool _closed = false;
bool _empty = true;
_FilterSink(ByteConversionSink this._sink, _Filter this._filter);
void add(List<int> data) {
addSlice(data, 0, data.length, false);
}
void addSlice(List<int> data, int start, int end, bool isLast) {
if (_closed) return;
if (start < 0 || start > data.length) {
throw new ArgumentError("Invalid start position");
}
if (end < 0 || end > data.length || end < start) {
throw new ArgumentError("Invalid end position");
}
try {
_empty = false;
_filter.process(data, start, end);
var out;
while ((out = _filter.processed(flush: false)) != null) {
_sink.add(out);
}
} catch (e) {
_closed = true;
throw e;
}
if (isLast) close();
}
void close() {
if (_closed) return;
// Be sure to send process an empty chunk of data. Without this, the empty
// message would not have a GZip frame (if compressed with GZip).
if (_empty) _filter.process(const [], 0, 0);
try {
var out;
while ((out = _filter.processed(end: true)) != null) {
_sink.add(out);
}
} catch (e) {
_closed = true;
throw e;
}
if (!_closed) _filter.end();
_closed = true;
_sink.close();
}
}
/**
* Private helper-class to handle native filters.
*/
abstract class _Filter {
/**
* Call to process a chunk of data. A call to [process] should only be made
* when [processed] returns [null].
*/
void process(List<int> data, int start, int end);
/**
* Get a chunk of processed data. When there are no more data available,
* [processed] will return [null]. Set [flush] to [false] for non-final
* calls to improve performance of some filters.
*
* The last call to [processed] should have [end] set to [true]. This will make
* sure a 'end' packet is written on the stream.
*/
List<int> processed({bool flush: true, bool end: false});
/**
* Mark the filter as closed. Always call this method for any filter created
* to avoid leaking resources. [end] can be called at any time, but any
* successive calls to [process] or [processed] will fail.
*/
void end();
external static _Filter newZLibDeflateFilter(bool gzip, int level);
external static _Filter newZLibInflateFilter();
}