Skip to content

Commit 7e565a5

Browse files
committed
Add compressing streaming impl
1 parent 6c9a44d commit 7e565a5

File tree

1 file changed

+133
-4
lines changed

1 file changed

+133
-4
lines changed

src/stream.rs

Lines changed: 133 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
use crate::set_panic_hook;
1+
use crate::{set_panic_hook, Options};
2+
use brotli::enc::encode::{
3+
BrotliEncoderCompressStream, BrotliEncoderCreateInstance, BrotliEncoderDestroyInstance,
4+
BrotliEncoderIsFinished, BrotliEncoderOperation, BrotliEncoderParameter,
5+
BrotliEncoderSetParameter, BrotliEncoderStateStruct,
6+
};
27
use brotli::enc::StandardAlloc; // Re-exported from alloc_stdlib::StandardAlloc
3-
use brotli::{BrotliDecompressStream, BrotliResult, BrotliState};
8+
use brotli::{self, BrotliDecompressStream, BrotliResult, BrotliState};
49
use wasm_bindgen::prelude::*;
510

611
#[wasm_bindgen]
@@ -9,17 +14,141 @@ pub enum BrotliStreamResult {
914
/// The stream is just initialized and no input is provided currently.
1015
/// `BrotliResult` uses `ResultFailure = 0`, but as we will convert `ResultFailure` to a negative actual error code,
1116
/// 0 is reused as no input currently.
17+
/// As for Brotli compressing, since offical API does not provide a way to retrieve a detailed error code, -1 is used.
1218
Init = 0,
1319
ResultSuccess = 1,
1420
NeedsMoreInput = 2,
1521
NeedsMoreOutput = 3,
1622
}
1723

1824
#[wasm_bindgen]
19-
pub struct CompressStream {}
25+
pub struct CompressStream {
26+
state: BrotliEncoderStateStruct<StandardAlloc>,
27+
result: i32,
28+
total_out: usize,
29+
}
30+
31+
impl Drop for CompressStream {
32+
fn drop(&mut self) {
33+
BrotliEncoderDestroyInstance(&mut self.state);
34+
}
35+
}
2036

2137
#[wasm_bindgen]
22-
impl CompressStream {}
38+
impl CompressStream {
39+
#[wasm_bindgen(constructor)]
40+
pub fn new(raw_options: &JsValue) -> Result<CompressStream, JsValue> {
41+
set_panic_hook();
42+
let options: Options = if raw_options.is_undefined() {
43+
serde_json::from_str("{}").unwrap()
44+
} else if raw_options.is_object() {
45+
raw_options.into_serde().unwrap()
46+
} else {
47+
return Err(JsValue::from_str("Options is not an object"));
48+
};
49+
let alloc = StandardAlloc::default();
50+
let mut state = BrotliEncoderCreateInstance(alloc);
51+
BrotliEncoderSetParameter(
52+
&mut state,
53+
BrotliEncoderParameter::BROTLI_PARAM_QUALITY,
54+
options.quality as u32,
55+
);
56+
Ok(Self {
57+
state,
58+
result: BrotliStreamResult::Init as i32,
59+
total_out: 0,
60+
})
61+
}
62+
63+
pub fn compress(
64+
&mut self,
65+
input_opt: Option<Box<[u8]>>,
66+
output_size: usize,
67+
) -> Result<Box<[u8]>, JsValue> {
68+
let mut nop_callback = |_data: &mut brotli::interface::PredictionModeContextMap<
69+
brotli::interface::InputReferenceMut,
70+
>,
71+
_cmds: &mut [brotli::interface::StaticCommand],
72+
_mb: brotli::interface::InputPair,
73+
_mfv: &mut StandardAlloc| ();
74+
let mut output = vec![0; output_size];
75+
let mut input_offset = 0;
76+
let mut available_out = output_size;
77+
let mut output_offset = 0;
78+
match input_opt {
79+
Some(input) => {
80+
let op = BrotliEncoderOperation::BROTLI_OPERATION_PROCESS;
81+
let mut available_in = input.len();
82+
let ret = BrotliEncoderCompressStream(
83+
&mut self.state,
84+
op,
85+
&mut available_in,
86+
&input,
87+
&mut input_offset,
88+
&mut available_out,
89+
&mut output,
90+
&mut output_offset,
91+
&mut Some(self.total_out),
92+
&mut nop_callback,
93+
);
94+
if ret != 0 {
95+
if available_out == 0 {
96+
self.result = BrotliStreamResult::NeedsMoreOutput as i32;
97+
Ok(output.into_boxed_slice())
98+
} else if available_in == 0 {
99+
output.truncate(output_offset);
100+
self.result = BrotliStreamResult::NeedsMoreInput as i32;
101+
Ok(output.into_boxed_slice())
102+
} else {
103+
self.result = -1;
104+
Err(JsValue::from_str("Unexpected Brotli streaming compress: both available_in & available_out are not 0 after a successful processing"))
105+
}
106+
} else {
107+
self.result = -1;
108+
Err(JsValue::from_str(
109+
"Brotli streaming compress failed: When processing",
110+
))
111+
}
112+
}
113+
None => {
114+
let op = BrotliEncoderOperation::BROTLI_OPERATION_FINISH;
115+
let input = Vec::new().into_boxed_slice();
116+
let mut available_in = 0;
117+
while BrotliEncoderIsFinished(&mut self.state) == 0 {
118+
let ret = BrotliEncoderCompressStream(
119+
&mut self.state,
120+
op,
121+
&mut available_in,
122+
&input,
123+
&mut input_offset,
124+
&mut available_out,
125+
&mut output,
126+
&mut output_offset,
127+
&mut Some(self.total_out),
128+
&mut nop_callback,
129+
);
130+
if ret == 0 {
131+
self.result = -1;
132+
return Err(JsValue::from_str(
133+
"Brotli streaming compress failed: When finishing",
134+
));
135+
}
136+
}
137+
output.truncate(output_offset);
138+
self.result = BrotliStreamResult::ResultSuccess as i32;
139+
Ok(output.into_boxed_slice())
140+
}
141+
}
142+
}
143+
144+
pub fn total_out(&self) -> usize {
145+
self.total_out
146+
}
147+
148+
pub fn result(&self) -> i32 {
149+
self.result
150+
}
151+
}
23152

24153
#[wasm_bindgen]
25154
pub struct DecompressStream {

0 commit comments

Comments
 (0)