-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Brotli4J compressor/decompressor (#3)
The official brotli JVM implementation only supports decompression. In order to support compression we introduce a new module that uses Brotli4J(https://github.com/hyperxpro/Brotli4j) instead. See also discussion: lhns/fs2-compress#151 Inspired by lhns/fs2-compress#152
- Loading branch information
Showing
7 changed files
with
256 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package zio.compress | ||
|
||
import com.aayushatharva.brotli4j.Brotli4jLoader | ||
import com.aayushatharva.brotli4j.encoder.{BrotliOutputStream, Encoder} | ||
import com.aayushatharva.brotli4j.decoder.BrotliInputStream | ||
import zio._ | ||
import zio.compress.BrotliMode._ | ||
import zio.compress.JavaIoInterop._ | ||
import zio.stream._ | ||
|
||
object Brotli4JCompressor { | ||
|
||
/** Make a pipeline that accepts a stream of bytes and produces a stream with Brotli compressed bytes. | ||
* | ||
* @param quality | ||
* The compression quality to use, or `None` for the default. | ||
* @param lgwin | ||
* log2(LZ window size) to use, or `None` for the default. | ||
* @param mode | ||
* type of encoding to use, or `None` for the default. | ||
*/ | ||
def make( | ||
quality: Option[BrotliQuality] = None, | ||
lgwin: Option[BrotliLogWindow] = None, | ||
mode: Option[BrotliMode] = None, | ||
): Brotli4JCompressor = | ||
new Brotli4JCompressor(quality, lgwin, mode) | ||
} | ||
|
||
class Brotli4JCompressor private ( | ||
quality: Option[BrotliQuality], | ||
lgwin: Option[BrotliLogWindow], | ||
mode: Option[BrotliMode], | ||
) extends Compressor { | ||
override def compress: ZPipeline[Any, Throwable, Byte, Byte] = | ||
BrotliLoader.ensureAvailability() >>> | ||
viaOutputStreamByte { outputStream => | ||
val brotliMode = mode.map { | ||
case Generic => Encoder.Mode.GENERIC | ||
case Text => Encoder.Mode.TEXT | ||
case Font => Encoder.Mode.FONT | ||
} | ||
val params = new Encoder.Parameters() | ||
.setQuality(quality.map(_.level).getOrElse(-1)) | ||
.setWindow(lgwin.map(_.lgwin).getOrElse(-1)) | ||
.setMode(brotliMode.orNull) | ||
new BrotliOutputStream(outputStream, params) | ||
} | ||
} | ||
|
||
object Brotli4JDecompressor { | ||
|
||
/** Makes a pipeline that accepts a Brotli compressed byte stream and produces a decompressed byte stream. | ||
* | ||
* @param chunkSize | ||
* The maximum chunk size of the outgoing ZStream. Defaults to `ZStream.DefaultChunkSize` (4KiB). | ||
*/ | ||
def make( | ||
chunkSize: Int = ZStream.DefaultChunkSize | ||
): Brotli4JDecompressor = | ||
new Brotli4JDecompressor(chunkSize) | ||
} | ||
|
||
class Brotli4JDecompressor private (chunkSize: Int) extends Decompressor { | ||
override def decompress: ZPipeline[Any, Throwable, Byte, Byte] = | ||
BrotliLoader.ensureAvailability() >>> | ||
viaInputStreamByte(chunkSize) { inputStream => | ||
new BrotliInputStream(inputStream) | ||
} | ||
} | ||
|
||
private object BrotliLoader { | ||
// Trigger loading of the Brotli4j native library | ||
new Brotli4jLoader() | ||
|
||
def ensureAvailability(): ZPipeline[Any, Throwable, Byte, Byte] = | ||
ZPipeline.unwrap { | ||
ZIO | ||
.attemptBlocking(Brotli4jLoader.ensureAvailability()) | ||
.as(ZPipeline.identity[Byte]) | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
brotli4j/src/main/scala/zio/compress/BrotliLogWindow.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package zio.compress | ||
|
||
/** Brotli log Window size. | ||
* | ||
* @param lgwin | ||
* lgwin log2(LZ window size), valid values: 10 to 24 | ||
*/ | ||
final case class BrotliLogWindow private (lgwin: Int) | ||
|
||
object BrotliLogWindow { | ||
|
||
/** Makes a valid Brotli log Window size. | ||
* | ||
* @param lgwin | ||
* lgwin log2(LZ window size), valid values: 10 to 24 | ||
* @return | ||
* a [[BrotliLogWindow]] or `None` if the level is not valid | ||
*/ | ||
def apply(lgwin: Int): Option[BrotliLogWindow] = | ||
if (10 <= lgwin && lgwin <= 24) Some(new BrotliLogWindow(lgwin)) else None | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package zio.compress | ||
|
||
sealed trait BrotliMode | ||
|
||
object BrotliMode { | ||
|
||
/** Default compression mode. In this mode compressor does not know anything in advance about the properties of the | ||
* input. | ||
*/ | ||
case object Generic extends BrotliMode | ||
|
||
/** Compression mode for UTF-8 formatted text input. | ||
*/ | ||
case object Text extends BrotliMode | ||
|
||
/** Compression mode used in WOFF 2.0. | ||
*/ | ||
case object Font extends BrotliMode | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package zio.compress | ||
|
||
/** Brotli compression level. | ||
* | ||
* @param level | ||
* compression level, valid values: 0 to 11 | ||
*/ | ||
final case class BrotliQuality private (level: Int) | ||
|
||
object BrotliQuality { | ||
|
||
/** Makes a Brotli compression level. | ||
* | ||
* @param level | ||
* compression level, valid values: 0 to 11 | ||
* @return | ||
* a [[BrotliQuality]] or `None` if the level is not valid | ||
*/ | ||
def apply(level: Int): Option[BrotliQuality] = | ||
if (0 <= level && level <= 11) Some(new BrotliQuality(level)) else None | ||
|
||
val Quality0 = new BrotliQuality(0) | ||
val Quality1 = new BrotliQuality(1) | ||
val Quality2 = new BrotliQuality(2) | ||
val Quality3 = new BrotliQuality(3) | ||
val Quality4 = new BrotliQuality(4) | ||
val Quality5 = new BrotliQuality(5) | ||
val Quality6 = new BrotliQuality(6) | ||
val Quality7 = new BrotliQuality(7) | ||
val Quality8 = new BrotliQuality(8) | ||
val Quality9 = new BrotliQuality(9) | ||
val Quality10 = new BrotliQuality(10) | ||
val Quality11 = new BrotliQuality(11) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package zio.compress | ||
|
||
import zio._ | ||
import zio.stream._ | ||
import zio.test._ | ||
|
||
import java.nio.charset.StandardCharsets.UTF_8 | ||
import java.util.Base64 | ||
|
||
object Brotli4JSpec extends ZIOSpecDefault { | ||
private val clear = Chunk.fromArray("hello world!".getBytes(UTF_8)) | ||
private val compressed = Chunk.fromArray(Base64.getDecoder.decode("iwWAaGVsbG8gd29ybGQhAw==")) | ||
|
||
override def spec: Spec[TestEnvironment with Scope, Any] = | ||
suite("Brotli4J")( | ||
test("brotli4J decompress") { | ||
for { | ||
obtained <- ZStream | ||
.fromChunk(compressed) | ||
.via(Brotli4JDecompressor.make().decompress) | ||
.runCollect | ||
} yield assertTrue(clear == obtained) | ||
}, | ||
test("brotli4J round trip") { | ||
checkN(10)(Gen.int(40, 5000), Gen.chunkOfBounded(0, 20000)(Gen.byte)) { (chunkSize, genBytes) => | ||
for { | ||
obtained <- ZStream | ||
.fromChunk(genBytes) | ||
.rechunk(chunkSize) | ||
.via(Brotli4JCompressor.make().compress) | ||
.via(Brotli4JDecompressor.make().decompress) | ||
.runCollect | ||
} yield assertTrue(obtained == genBytes) | ||
} | ||
}, | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters