roxify 1.13.2 → 1.13.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/native/lib.rs CHANGED
@@ -69,51 +69,51 @@ pub fn native_adler32(buffer: Buffer) -> u32 {
69
69
 
70
70
  #[cfg(not(test))]
71
71
  #[napi]
72
- pub fn native_delta_encode(buffer: Buffer) -> Vec<u8> {
73
- core::delta_encode_bytes(&buffer)
72
+ pub fn native_delta_encode(buffer: Buffer) -> Buffer {
73
+ core::delta_encode_bytes(&buffer).into()
74
74
  }
75
75
 
76
76
  #[cfg(not(test))]
77
77
  #[napi]
78
- pub fn native_delta_decode(buffer: Buffer) -> Vec<u8> {
79
- core::delta_decode_bytes(&buffer)
78
+ pub fn native_delta_decode(buffer: Buffer) -> Buffer {
79
+ core::delta_decode_bytes(&buffer).into()
80
80
  }
81
81
 
82
82
  #[cfg(not(test))]
83
83
  #[napi]
84
- pub fn native_zstd_compress(buffer: Buffer, level: i32) -> Result<Vec<u8>> {
85
- core::zstd_compress_bytes(&buffer, level, None).map_err(|e| Error::from_reason(e))
84
+ pub fn native_zstd_compress(buffer: Buffer, level: i32) -> Result<Buffer> {
85
+ core::zstd_compress_bytes(&buffer, level, None).map(Buffer::from).map_err(|e| Error::from_reason(e))
86
86
  }
87
87
 
88
88
  #[cfg(not(test))]
89
89
  #[napi]
90
- pub fn native_zstd_compress_with_dict(buffer: Buffer, level: i32, dict: Buffer) -> Result<Vec<u8>> {
90
+ pub fn native_zstd_compress_with_dict(buffer: Buffer, level: i32, dict: Buffer) -> Result<Buffer> {
91
91
  let dict_slice: &[u8] = &dict;
92
- core::zstd_compress_bytes(&buffer, level, Some(dict_slice)).map_err(|e| Error::from_reason(e))
92
+ core::zstd_compress_bytes(&buffer, level, Some(dict_slice)).map(Buffer::from).map_err(|e| Error::from_reason(e))
93
93
  }
94
94
 
95
95
  #[cfg(not(test))]
96
96
  #[napi]
97
- pub fn native_zstd_decompress(buffer: Buffer) -> Result<Vec<u8>> {
98
- core::zstd_decompress_bytes(&buffer, None).map_err(|e| Error::from_reason(e))
97
+ pub fn native_zstd_decompress(buffer: Buffer) -> Result<Buffer> {
98
+ core::zstd_decompress_bytes(&buffer, None).map(Buffer::from).map_err(|e| Error::from_reason(e))
99
99
  }
100
100
 
101
101
  #[cfg(not(test))]
102
102
  #[napi]
103
- pub fn native_zstd_decompress_with_dict(buffer: Buffer, dict: Buffer) -> Result<Vec<u8>> {
103
+ pub fn native_zstd_decompress_with_dict(buffer: Buffer, dict: Buffer) -> Result<Buffer> {
104
104
  let dict_slice: &[u8] = &dict;
105
- core::zstd_decompress_bytes(&buffer, Some(dict_slice)).map_err(|e| Error::from_reason(e))
105
+ core::zstd_decompress_bytes(&buffer, Some(dict_slice)).map(Buffer::from).map_err(|e| Error::from_reason(e))
106
106
  }
107
107
 
108
108
  #[cfg(not(test))]
109
109
  #[napi]
110
- pub fn bwt_transform(buffer: Buffer) -> Result<Vec<u8>> {
110
+ pub fn bwt_transform(buffer: Buffer) -> Result<Buffer> {
111
111
  match bwt::bwt_encode(&buffer) {
112
112
  Ok(result) => {
113
113
  let mut output = Vec::with_capacity(4 + result.transformed.len());
114
114
  output.extend_from_slice(&result.primary_index.to_le_bytes());
115
115
  output.extend_from_slice(&result.transformed);
116
- Ok(output)
116
+ Ok(output.into())
117
117
  }
118
118
  Err(e) => Err(Error::from_reason(e.to_string())),
119
119
  }
@@ -191,15 +191,17 @@ mod tests {
191
191
 
192
192
  #[cfg(not(test))]
193
193
  #[napi]
194
- pub fn native_encode_png(buffer: Buffer, compression_level: i32) -> Result<Vec<u8>> {
194
+ pub fn native_encode_png(buffer: Buffer, compression_level: i32) -> Result<Buffer> {
195
195
  encoder::encode_to_png(&buffer, compression_level)
196
+ .map(Buffer::from)
196
197
  .map_err(|e| Error::from_reason(e.to_string()))
197
198
  }
198
199
 
199
200
  #[cfg(not(test))]
200
201
  #[napi]
201
- pub fn native_encode_png_raw(buffer: Buffer, compression_level: i32) -> Result<Vec<u8>> {
202
+ pub fn native_encode_png_raw(buffer: Buffer, compression_level: i32) -> Result<Buffer> {
202
203
  encoder::encode_to_png_raw(&buffer, compression_level)
204
+ .map(Buffer::from)
203
205
  .map_err(|e| Error::from_reason(e.to_string()))
204
206
  }
205
207
 
@@ -210,13 +212,14 @@ pub fn native_encode_png_with_name_and_filelist(
210
212
  compression_level: i32,
211
213
  name: Option<String>,
212
214
  file_list_json: Option<String>,
213
- ) -> Result<Vec<u8>> {
215
+ ) -> Result<Buffer> {
214
216
  encoder::encode_to_png_with_name_and_filelist(
215
217
  &buffer,
216
218
  compression_level,
217
219
  name.as_deref(),
218
220
  file_list_json.as_deref(),
219
221
  )
222
+ .map(Buffer::from)
220
223
  .map_err(|e| Error::from_reason(e.to_string()))
221
224
  }
222
225
 
@@ -229,7 +232,7 @@ pub fn native_encode_png_with_encryption_name_and_filelist(
229
232
  encrypt_type: Option<String>,
230
233
  name: Option<String>,
231
234
  file_list_json: Option<String>,
232
- ) -> Result<Vec<u8>> {
235
+ ) -> Result<Buffer> {
233
236
  encoder::encode_to_png_with_encryption_name_and_filelist(
234
237
  &buffer,
235
238
  compression_level,
@@ -238,13 +241,14 @@ pub fn native_encode_png_with_encryption_name_and_filelist(
238
241
  name.as_deref(),
239
242
  file_list_json.as_deref(),
240
243
  )
244
+ .map(Buffer::from)
241
245
  .map_err(|e| Error::from_reason(e.to_string()))
242
246
  }
243
247
 
244
248
  #[napi(object)]
245
249
  pub struct PngChunkData {
246
250
  pub name: String,
247
- pub data: Vec<u8>,
251
+ pub data: Buffer,
248
252
  }
249
253
 
250
254
  #[cfg(not(test))]
@@ -255,21 +259,22 @@ pub fn extract_png_chunks(png_buffer: Buffer) -> Result<Vec<PngChunkData>> {
255
259
 
256
260
  Ok(chunks.into_iter().map(|c| PngChunkData {
257
261
  name: c.name,
258
- data: c.data,
262
+ data: c.data.into(),
259
263
  }).collect())
260
264
  }
261
265
 
262
266
  #[cfg(not(test))]
263
267
  #[napi]
264
- pub fn encode_png_chunks(chunks: Vec<PngChunkData>) -> Result<Vec<u8>> {
268
+ pub fn encode_png_chunks(chunks: Vec<PngChunkData>) -> Result<Buffer> {
265
269
  let native_chunks: Vec<png_utils::PngChunk> = chunks.into_iter()
266
270
  .map(|c| png_utils::PngChunk {
267
271
  name: c.name,
268
- data: c.data,
272
+ data: c.data.to_vec(),
269
273
  })
270
274
  .collect();
271
275
 
272
276
  png_utils::encode_png_chunks(&native_chunks)
277
+ .map(Buffer::from)
273
278
  .map_err(|e| Error::from_reason(e))
274
279
  }
275
280
 
@@ -309,22 +314,23 @@ pub fn sharp_resize_image(
309
314
  width: u32,
310
315
  height: u32,
311
316
  kernel: String,
312
- ) -> Result<Vec<u8>> {
317
+ ) -> Result<Buffer> {
313
318
  image_utils::sharp_resize(&input_buffer, width, height, &kernel)
319
+ .map(Buffer::from)
314
320
  .map_err(|e| Error::from_reason(e))
315
321
  }
316
322
 
317
323
  #[cfg(not(test))]
318
324
  #[napi]
319
- pub fn sharp_raw_pixels(input_buffer: Buffer) -> Result<Vec<u8>> {
325
+ pub fn sharp_raw_pixels(input_buffer: Buffer) -> Result<Buffer> {
320
326
  let (pixels, _w, _h) = image_utils::sharp_raw_pixels(&input_buffer)
321
327
  .map_err(|e| Error::from_reason(e))?;
322
- Ok(pixels)
328
+ Ok(pixels.into())
323
329
  }
324
330
 
325
331
  #[napi(object)]
326
332
  pub struct RawPixelsWithDimensions {
327
- pub pixels: Vec<u8>,
333
+ pub pixels: Buffer,
328
334
  pub width: u32,
329
335
  pub height: u32,
330
336
  }
@@ -334,7 +340,7 @@ pub struct RawPixelsWithDimensions {
334
340
  pub fn sharp_to_raw(input_buffer: Buffer) -> Result<RawPixelsWithDimensions> {
335
341
  let (pixels, width, height) = image_utils::sharp_raw_pixels(&input_buffer)
336
342
  .map_err(|e| Error::from_reason(e))?;
337
- Ok(RawPixelsWithDimensions { pixels, width, height })
343
+ Ok(RawPixelsWithDimensions { pixels: pixels.into(), width, height })
338
344
  }
339
345
 
340
346
  #[cfg(not(test))]
@@ -347,8 +353,9 @@ pub fn sharp_metadata(input_buffer: Buffer) -> Result<SharpMetadata> {
347
353
 
348
354
  #[cfg(not(test))]
349
355
  #[napi]
350
- pub fn rgb_to_png(rgb_buffer: Buffer, width: u32, height: u32) -> Result<Vec<u8>> {
356
+ pub fn rgb_to_png(rgb_buffer: Buffer, width: u32, height: u32) -> Result<Buffer> {
351
357
  image_utils::rgb_to_png(&rgb_buffer, width, height)
358
+ .map(Buffer::from)
352
359
  .map_err(|e| Error::from_reason(e))
353
360
  }
354
361
 
@@ -357,27 +364,30 @@ pub fn rgb_to_png(rgb_buffer: Buffer, width: u32, height: u32) -> Result<Vec<u8>
357
364
  pub fn png_to_rgb(png_buffer: Buffer) -> Result<RawPixelsWithDimensions> {
358
365
  let (pixels, width, height) = image_utils::png_to_rgb(&png_buffer)
359
366
  .map_err(|e| Error::from_reason(e))?;
360
- Ok(RawPixelsWithDimensions { pixels, width, height })
367
+ Ok(RawPixelsWithDimensions { pixels: pixels.into(), width, height })
361
368
  }
362
369
 
363
370
  #[cfg(not(test))]
364
371
  #[napi]
365
- pub fn crop_and_reconstitute(png_buffer: Buffer) -> Result<Vec<u8>> {
372
+ pub fn crop_and_reconstitute(png_buffer: Buffer) -> Result<Buffer> {
366
373
  reconstitution::crop_and_reconstitute(&png_buffer)
374
+ .map(Buffer::from)
367
375
  .map_err(|e| Error::from_reason(e))
368
376
  }
369
377
 
370
378
  #[cfg(not(test))]
371
379
  #[napi]
372
- pub fn unstretch_nn(png_buffer: Buffer) -> Result<Vec<u8>> {
380
+ pub fn unstretch_nn(png_buffer: Buffer) -> Result<Buffer> {
373
381
  reconstitution::unstretch_nn(&png_buffer)
382
+ .map(Buffer::from)
374
383
  .map_err(|e| Error::from_reason(e))
375
384
  }
376
385
 
377
386
  #[cfg(not(test))]
378
387
  #[napi]
379
- pub fn extract_payload_from_png(png_buffer: Buffer) -> Result<Vec<u8>> {
388
+ pub fn extract_payload_from_png(png_buffer: Buffer) -> Result<Buffer> {
380
389
  png_utils::extract_payload_from_png(&png_buffer)
390
+ .map(Buffer::from)
381
391
  .map_err(|e| Error::from_reason(e))
382
392
  }
383
393
 
@@ -392,8 +402,9 @@ pub fn extract_file_list_from_pixels(png_buffer: Buffer) -> Result<String> {
392
402
 
393
403
  #[cfg(not(test))]
394
404
  #[napi]
395
- pub fn native_encode_wav(buffer: Buffer, compression_level: i32) -> Result<Vec<u8>> {
405
+ pub fn native_encode_wav(buffer: Buffer, compression_level: i32) -> Result<Buffer> {
396
406
  encoder::encode_to_wav(&buffer, compression_level)
407
+ .map(Buffer::from)
397
408
  .map_err(|e| Error::from_reason(e.to_string()))
398
409
  }
399
410
 
@@ -404,13 +415,14 @@ pub fn native_encode_wav_with_name_and_filelist(
404
415
  compression_level: i32,
405
416
  name: Option<String>,
406
417
  file_list_json: Option<String>,
407
- ) -> Result<Vec<u8>> {
418
+ ) -> Result<Buffer> {
408
419
  encoder::encode_to_wav_with_name_and_filelist(
409
420
  &buffer,
410
421
  compression_level,
411
422
  name.as_deref(),
412
423
  file_list_json.as_deref(),
413
424
  )
425
+ .map(Buffer::from)
414
426
  .map_err(|e| Error::from_reason(e.to_string()))
415
427
  }
416
428
 
@@ -423,7 +435,7 @@ pub fn native_encode_wav_with_encryption_name_and_filelist(
423
435
  encrypt_type: Option<String>,
424
436
  name: Option<String>,
425
437
  file_list_json: Option<String>,
426
- ) -> Result<Vec<u8>> {
438
+ ) -> Result<Buffer> {
427
439
  encoder::encode_to_wav_with_encryption_name_and_filelist(
428
440
  &buffer,
429
441
  compression_level,
@@ -432,26 +444,29 @@ pub fn native_encode_wav_with_encryption_name_and_filelist(
432
444
  name.as_deref(),
433
445
  file_list_json.as_deref(),
434
446
  )
447
+ .map(Buffer::from)
435
448
  .map_err(|e| Error::from_reason(e.to_string()))
436
449
  }
437
450
 
438
451
  #[cfg(not(test))]
439
452
  #[napi]
440
- pub fn native_decode_wav_payload(wav_buffer: Buffer) -> Result<Vec<u8>> {
453
+ pub fn native_decode_wav_payload(wav_buffer: Buffer) -> Result<Buffer> {
441
454
  encoder::decode_wav_payload(&wav_buffer)
455
+ .map(Buffer::from)
442
456
  .map_err(|e| Error::from_reason(e.to_string()))
443
457
  }
444
458
 
445
459
  #[cfg(not(test))]
446
460
  #[napi]
447
- pub fn native_bytes_to_wav(buffer: Buffer) -> Vec<u8> {
448
- audio::bytes_to_wav(&buffer)
461
+ pub fn native_bytes_to_wav(buffer: Buffer) -> Buffer {
462
+ audio::bytes_to_wav(&buffer).into()
449
463
  }
450
464
 
451
465
  #[cfg(not(test))]
452
466
  #[napi]
453
- pub fn native_wav_to_bytes(wav_buffer: Buffer) -> Result<Vec<u8>> {
467
+ pub fn native_wav_to_bytes(wav_buffer: Buffer) -> Result<Buffer> {
454
468
  audio::wav_to_bytes(&wav_buffer)
469
+ .map(Buffer::from)
455
470
  .map_err(|e| Error::from_reason(e))
456
471
  }
457
472
 
package/native/main.rs CHANGED
@@ -343,10 +343,16 @@ fn main() -> anyhow::Result<()> {
343
343
  && sig == [137, 80, 78, 71, 13, 10, 26, 10]
344
344
  });
345
345
 
346
- if is_png_file && files.is_none() && dict.is_none() && file_size > 100_000_000 {
346
+ if is_png_file && files.is_none() && dict.is_none() {
347
347
  let out_dir = output.clone().unwrap_or_else(|| PathBuf::from("out.raw"));
348
- eprintln!("PROGRESS:5:100:decoding");
349
- match streaming_decode::streaming_decode_to_dir_encrypted(&input, &out_dir, passphrase.as_deref()) {
348
+ match streaming_decode::streaming_decode_to_dir_encrypted_with_progress(
349
+ &input,
350
+ &out_dir,
351
+ passphrase.as_deref(),
352
+ Some(Box::new(|current, total, step| {
353
+ eprintln!("PROGRESS:{}:{}:{}", current, total, step);
354
+ })),
355
+ ) {
350
356
  Ok(written) => {
351
357
  eprintln!("PROGRESS:100:100:done");
352
358
  println!("Unpacked {} files (TAR)", written.len());
@@ -7,14 +7,25 @@ const MARKER_BYTES: usize = 12;
7
7
 
8
8
  type Aes256Ctr = ctr::Ctr64BE<aes::Aes256>;
9
9
 
10
+ pub type DecodeProgressCallback = Box<dyn Fn(u64, u64, &str) + Send>;
11
+
10
12
  pub fn streaming_decode_to_dir(png_path: &Path, out_dir: &Path) -> Result<Vec<String>, String> {
11
- streaming_decode_to_dir_encrypted(png_path, out_dir, None)
13
+ streaming_decode_to_dir_encrypted_with_progress(png_path, out_dir, None, None)
12
14
  }
13
15
 
14
16
  pub fn streaming_decode_to_dir_encrypted(
15
17
  png_path: &Path,
16
18
  out_dir: &Path,
17
19
  passphrase: Option<&str>,
20
+ ) -> Result<Vec<String>, String> {
21
+ streaming_decode_to_dir_encrypted_with_progress(png_path, out_dir, passphrase, None)
22
+ }
23
+
24
+ pub fn streaming_decode_to_dir_encrypted_with_progress(
25
+ png_path: &Path,
26
+ out_dir: &Path,
27
+ passphrase: Option<&str>,
28
+ progress: Option<DecodeProgressCallback>,
18
29
  ) -> Result<Vec<String>, String> {
19
30
  let file = std::fs::File::open(png_path).map_err(|e| format!("open: {}", e))?;
20
31
  let mmap = unsafe { memmap2::Mmap::map(&file).map_err(|e| format!("mmap: {}", e))? };
@@ -24,7 +35,16 @@ pub fn streaming_decode_to_dir_encrypted(
24
35
  return Err("Not a PNG file".into());
25
36
  }
26
37
 
38
+ if let Some(ref cb) = progress {
39
+ cb(2, 100, "parsing_png");
40
+ }
41
+
27
42
  let (width, height, idat_data_start, idat_data_end) = parse_png_header(data)?;
43
+ let total_expected = parse_rxfl_total_bytes(data).unwrap_or(0);
44
+
45
+ if let Some(ref cb) = progress {
46
+ cb(5, 100, "reading_header");
47
+ }
28
48
 
29
49
  let mut reader = DeflatePixelReader::new(data, width, height, idat_data_start, idat_data_end);
30
50
 
@@ -51,6 +71,10 @@ pub fn streaming_decode_to_dir_encrypted(
51
71
  reader.read_exact(&mut plen_buf).map_err(|e| format!("read payload_len: {}", e))?;
52
72
  let payload_len = u32::from_be_bytes(plen_buf) as u64;
53
73
 
74
+ if let Some(ref cb) = progress {
75
+ cb(8, 100, "decrypting");
76
+ }
77
+
54
78
  let payload_reader = reader.take(payload_len);
55
79
 
56
80
  let first_byte_reader = FirstByteReader::new(payload_reader);
@@ -58,10 +82,13 @@ pub fn streaming_decode_to_dir_encrypted(
58
82
 
59
83
  match enc_byte {
60
84
  0x00 => {
85
+ if let Some(ref cb) = progress {
86
+ cb(10, 100, "decompressing");
87
+ }
61
88
  let mut decoder = zstd::stream::Decoder::new(remaining_reader)
62
89
  .map_err(|e| format!("zstd decoder: {}", e))?;
63
90
  decoder.window_log_max(31).map_err(|e| format!("zstd window_log_max: {}", e))?;
64
- read_rox1_and_untar(decoder, out_dir)
91
+ read_rox1_and_untar_with_progress(decoder, out_dir, progress, total_expected)
65
92
  }
66
93
  0x03 => {
67
94
  let pass = passphrase.ok_or("Passphrase required for AES-CTR decryption")?;
@@ -79,23 +106,31 @@ pub fn streaming_decode_to_dir_encrypted(
79
106
  let encrypted_data_len = payload_len - 1 - 16 - 16 - hmac_size;
80
107
  let ctr_reader = CtrDecryptReader::new(r.take(encrypted_data_len), cipher);
81
108
 
109
+ if let Some(ref cb) = progress {
110
+ cb(10, 100, "decompressing");
111
+ }
82
112
  let mut decoder = zstd::stream::Decoder::new(ctr_reader)
83
113
  .map_err(|e| format!("zstd decoder: {}", e))?;
84
114
  decoder.window_log_max(31).map_err(|e| format!("zstd window_log_max: {}", e))?;
85
- read_rox1_and_untar(decoder, out_dir)
115
+ read_rox1_and_untar_with_progress(decoder, out_dir, progress, total_expected)
86
116
  }
87
117
  _ => Err(format!("Unsupported encryption (enc=0x{:02x}) in streaming decode", enc_byte)),
88
118
  }
89
119
  }
90
120
 
91
- fn read_rox1_and_untar<R: Read>(mut decoder: R, out_dir: &Path) -> Result<Vec<String>, String> {
121
+ fn read_rox1_and_untar_with_progress<R: Read>(
122
+ mut decoder: R,
123
+ out_dir: &Path,
124
+ progress: Option<DecodeProgressCallback>,
125
+ total_expected: u64,
126
+ ) -> Result<Vec<String>, String> {
92
127
  let mut magic = [0u8; 4];
93
128
  decoder.read_exact(&mut magic).map_err(|e| format!("read ROX1: {}", e))?;
94
129
  if &magic != b"ROX1" {
95
130
  return Err(format!("Expected ROX1, got {:?}", magic));
96
131
  }
97
132
  std::fs::create_dir_all(out_dir).map_err(|e| format!("mkdir: {}", e))?;
98
- tar_unpack_from_reader(decoder, out_dir)
133
+ tar_unpack_from_reader_with_progress(decoder, out_dir, progress, total_expected)
99
134
  }
100
135
 
101
136
  fn parse_png_header(data: &[u8]) -> Result<(usize, usize, usize, usize), String> {
@@ -147,6 +182,30 @@ fn parse_png_header(data: &[u8]) -> Result<(usize, usize, usize, usize), String>
147
182
  Ok((width, height, idat_start, idat_end))
148
183
  }
149
184
 
185
+ fn parse_rxfl_total_bytes(data: &[u8]) -> Option<u64> {
186
+ let mut pos = 8;
187
+ while pos + 12 <= data.len() {
188
+ let chunk_len = u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]) as usize;
189
+ let chunk_type = &data[pos + 4..pos + 8];
190
+ let chunk_data_start = pos + 8;
191
+
192
+ if chunk_type == b"rXFL" && chunk_data_start + chunk_len <= data.len() {
193
+ let json_bytes = &data[chunk_data_start..chunk_data_start + chunk_len];
194
+ if let Ok(entries) = serde_json::from_slice::<Vec<serde_json::Value>>(json_bytes) {
195
+ let total: u64 = entries.iter()
196
+ .filter_map(|e| e.get("size").and_then(|s| s.as_u64()))
197
+ .sum();
198
+ return Some(total);
199
+ }
200
+ } else if chunk_type == b"IEND" {
201
+ break;
202
+ }
203
+
204
+ pos = chunk_data_start + chunk_len + 4;
205
+ }
206
+ None
207
+ }
208
+
150
209
  struct DeflatePixelReader<'a> {
151
210
  data: &'a [u8],
152
211
  height: usize,
@@ -298,15 +357,23 @@ impl<R: Read> Read for CtrDecryptReader<R> {
298
357
  }
299
358
  }
300
359
 
301
- fn tar_unpack_from_reader<R: Read>(reader: R, output_dir: &Path) -> Result<Vec<String>, String> {
360
+ fn tar_unpack_from_reader_with_progress<R: Read>(
361
+ reader: R,
362
+ output_dir: &Path,
363
+ progress: Option<DecodeProgressCallback>,
364
+ total_expected: u64,
365
+ ) -> Result<Vec<String>, String> {
302
366
  let buf_reader = std::io::BufReader::with_capacity(8 * 1024 * 1024, reader);
303
367
  let mut archive = tar::Archive::new(buf_reader);
304
368
  let mut written = Vec::new();
305
369
  let mut created_dirs = std::collections::HashSet::new();
370
+ let mut bytes_extracted: u64 = 0;
371
+ let mut last_pct: u64 = 10;
306
372
 
307
373
  let entries = archive.entries().map_err(|e| format!("tar entries: {}", e))?;
308
374
  for entry in entries {
309
375
  let mut entry = entry.map_err(|e| format!("tar entry: {}", e))?;
376
+ let entry_size = entry.size();
310
377
  let path = entry.path().map_err(|e| format!("tar path: {}", e))?.to_path_buf();
311
378
 
312
379
  let mut safe = std::path::PathBuf::new();
@@ -327,11 +394,28 @@ fn tar_unpack_from_reader<R: Read>(reader: R, output_dir: &Path) -> Result<Vec<S
327
394
  }
328
395
 
329
396
  let mut f = std::io::BufWriter::with_capacity(
330
- (entry.size() as usize).min(4 * 1024 * 1024).max(8192),
397
+ (entry_size as usize).min(4 * 1024 * 1024).max(8192),
331
398
  std::fs::File::create(&dest).map_err(|e| format!("create {:?}: {}", dest, e))?,
332
399
  );
333
400
  std::io::copy(&mut entry, &mut f).map_err(|e| format!("write {:?}: {}", dest, e))?;
334
401
  written.push(safe.to_string_lossy().to_string());
402
+
403
+ bytes_extracted += entry_size;
404
+ if let Some(ref cb) = progress {
405
+ let pct = if total_expected > 0 {
406
+ 10 + (bytes_extracted * 89 / total_expected).min(89)
407
+ } else {
408
+ (10 + (bytes_extracted / (1024 * 1024))).min(99)
409
+ };
410
+ if pct > last_pct {
411
+ last_pct = pct;
412
+ cb(pct, 100, "extracting");
413
+ }
414
+ }
415
+ }
416
+
417
+ if let Some(ref cb) = progress {
418
+ cb(99, 100, "finishing");
335
419
  }
336
420
 
337
421
  Ok(written)