roxify 1.13.1 → 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
@@ -6,8 +6,6 @@ use napi_derive::napi;
6
6
  static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
7
7
 
8
8
  mod core;
9
- #[cfg(feature = "gpu")]
10
- mod gpu;
11
9
  mod rans;
12
10
  mod rans_byte;
13
11
  mod bwt;
@@ -27,15 +25,6 @@ mod archive;
27
25
  mod streaming;
28
26
 
29
27
  pub use core::*;
30
- #[cfg(feature = "gpu")]
31
- pub use gpu::*;
32
- #[cfg(not(feature = "gpu"))]
33
- mod gpu {
34
- pub fn gpu_available() -> bool {
35
- false
36
- }
37
- }
38
-
39
28
  pub use rans::*;
40
29
  pub use bwt::*;
41
30
  pub use context_mixing::*;
@@ -57,12 +46,6 @@ pub struct CompressionReport {
57
46
  pub blocks_count: u32,
58
47
  }
59
48
 
60
- #[napi(object)]
61
- pub struct GpuStatus {
62
- pub available: bool,
63
- pub adapter_info: Option<String>,
64
- }
65
-
66
49
  #[cfg(not(test))]
67
50
  #[napi]
68
51
  pub fn scan_pixels(buffer: Buffer, channels: u32, marker_bytes: Option<Buffer>) -> Result<ScanResult> {
@@ -86,60 +69,51 @@ pub fn native_adler32(buffer: Buffer) -> u32 {
86
69
 
87
70
  #[cfg(not(test))]
88
71
  #[napi]
89
- pub fn native_delta_encode(buffer: Buffer) -> Vec<u8> {
90
- core::delta_encode_bytes(&buffer)
72
+ pub fn native_delta_encode(buffer: Buffer) -> Buffer {
73
+ core::delta_encode_bytes(&buffer).into()
91
74
  }
92
75
 
93
76
  #[cfg(not(test))]
94
77
  #[napi]
95
- pub fn native_delta_decode(buffer: Buffer) -> Vec<u8> {
96
- core::delta_decode_bytes(&buffer)
78
+ pub fn native_delta_decode(buffer: Buffer) -> Buffer {
79
+ core::delta_decode_bytes(&buffer).into()
97
80
  }
98
81
 
99
82
  #[cfg(not(test))]
100
83
  #[napi]
101
- pub fn native_zstd_compress(buffer: Buffer, level: i32) -> Result<Vec<u8>> {
102
- 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))
103
86
  }
104
87
 
105
88
  #[cfg(not(test))]
106
89
  #[napi]
107
- 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> {
108
91
  let dict_slice: &[u8] = &dict;
109
- 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))
110
93
  }
111
94
 
112
95
  #[cfg(not(test))]
113
96
  #[napi]
114
- pub fn native_zstd_decompress(buffer: Buffer) -> Result<Vec<u8>> {
115
- 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))
116
99
  }
117
100
 
118
101
  #[cfg(not(test))]
119
102
  #[napi]
120
- 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> {
121
104
  let dict_slice: &[u8] = &dict;
122
- core::zstd_decompress_bytes(&buffer, Some(dict_slice)).map_err(|e| Error::from_reason(e))
123
- }
124
-
125
- #[cfg(not(test))]
126
- #[napi]
127
- pub fn check_gpu_status() -> GpuStatus {
128
- GpuStatus {
129
- available: gpu::gpu_available(),
130
- adapter_info: None,
131
- }
105
+ core::zstd_decompress_bytes(&buffer, Some(dict_slice)).map(Buffer::from).map_err(|e| Error::from_reason(e))
132
106
  }
133
107
 
134
108
  #[cfg(not(test))]
135
109
  #[napi]
136
- pub fn bwt_transform(buffer: Buffer) -> Result<Vec<u8>> {
110
+ pub fn bwt_transform(buffer: Buffer) -> Result<Buffer> {
137
111
  match bwt::bwt_encode(&buffer) {
138
112
  Ok(result) => {
139
113
  let mut output = Vec::with_capacity(4 + result.transformed.len());
140
114
  output.extend_from_slice(&result.primary_index.to_le_bytes());
141
115
  output.extend_from_slice(&result.transformed);
142
- Ok(output)
116
+ Ok(output.into())
143
117
  }
144
118
  Err(e) => Err(Error::from_reason(e.to_string())),
145
119
  }
@@ -217,15 +191,17 @@ mod tests {
217
191
 
218
192
  #[cfg(not(test))]
219
193
  #[napi]
220
- 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> {
221
195
  encoder::encode_to_png(&buffer, compression_level)
196
+ .map(Buffer::from)
222
197
  .map_err(|e| Error::from_reason(e.to_string()))
223
198
  }
224
199
 
225
200
  #[cfg(not(test))]
226
201
  #[napi]
227
- 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> {
228
203
  encoder::encode_to_png_raw(&buffer, compression_level)
204
+ .map(Buffer::from)
229
205
  .map_err(|e| Error::from_reason(e.to_string()))
230
206
  }
231
207
 
@@ -236,13 +212,14 @@ pub fn native_encode_png_with_name_and_filelist(
236
212
  compression_level: i32,
237
213
  name: Option<String>,
238
214
  file_list_json: Option<String>,
239
- ) -> Result<Vec<u8>> {
215
+ ) -> Result<Buffer> {
240
216
  encoder::encode_to_png_with_name_and_filelist(
241
217
  &buffer,
242
218
  compression_level,
243
219
  name.as_deref(),
244
220
  file_list_json.as_deref(),
245
221
  )
222
+ .map(Buffer::from)
246
223
  .map_err(|e| Error::from_reason(e.to_string()))
247
224
  }
248
225
 
@@ -255,7 +232,7 @@ pub fn native_encode_png_with_encryption_name_and_filelist(
255
232
  encrypt_type: Option<String>,
256
233
  name: Option<String>,
257
234
  file_list_json: Option<String>,
258
- ) -> Result<Vec<u8>> {
235
+ ) -> Result<Buffer> {
259
236
  encoder::encode_to_png_with_encryption_name_and_filelist(
260
237
  &buffer,
261
238
  compression_level,
@@ -264,13 +241,14 @@ pub fn native_encode_png_with_encryption_name_and_filelist(
264
241
  name.as_deref(),
265
242
  file_list_json.as_deref(),
266
243
  )
244
+ .map(Buffer::from)
267
245
  .map_err(|e| Error::from_reason(e.to_string()))
268
246
  }
269
247
 
270
248
  #[napi(object)]
271
249
  pub struct PngChunkData {
272
250
  pub name: String,
273
- pub data: Vec<u8>,
251
+ pub data: Buffer,
274
252
  }
275
253
 
276
254
  #[cfg(not(test))]
@@ -281,21 +259,22 @@ pub fn extract_png_chunks(png_buffer: Buffer) -> Result<Vec<PngChunkData>> {
281
259
 
282
260
  Ok(chunks.into_iter().map(|c| PngChunkData {
283
261
  name: c.name,
284
- data: c.data,
262
+ data: c.data.into(),
285
263
  }).collect())
286
264
  }
287
265
 
288
266
  #[cfg(not(test))]
289
267
  #[napi]
290
- pub fn encode_png_chunks(chunks: Vec<PngChunkData>) -> Result<Vec<u8>> {
268
+ pub fn encode_png_chunks(chunks: Vec<PngChunkData>) -> Result<Buffer> {
291
269
  let native_chunks: Vec<png_utils::PngChunk> = chunks.into_iter()
292
270
  .map(|c| png_utils::PngChunk {
293
271
  name: c.name,
294
- data: c.data,
272
+ data: c.data.to_vec(),
295
273
  })
296
274
  .collect();
297
275
 
298
276
  png_utils::encode_png_chunks(&native_chunks)
277
+ .map(Buffer::from)
299
278
  .map_err(|e| Error::from_reason(e))
300
279
  }
301
280
 
@@ -335,22 +314,23 @@ pub fn sharp_resize_image(
335
314
  width: u32,
336
315
  height: u32,
337
316
  kernel: String,
338
- ) -> Result<Vec<u8>> {
317
+ ) -> Result<Buffer> {
339
318
  image_utils::sharp_resize(&input_buffer, width, height, &kernel)
319
+ .map(Buffer::from)
340
320
  .map_err(|e| Error::from_reason(e))
341
321
  }
342
322
 
343
323
  #[cfg(not(test))]
344
324
  #[napi]
345
- pub fn sharp_raw_pixels(input_buffer: Buffer) -> Result<Vec<u8>> {
325
+ pub fn sharp_raw_pixels(input_buffer: Buffer) -> Result<Buffer> {
346
326
  let (pixels, _w, _h) = image_utils::sharp_raw_pixels(&input_buffer)
347
327
  .map_err(|e| Error::from_reason(e))?;
348
- Ok(pixels)
328
+ Ok(pixels.into())
349
329
  }
350
330
 
351
331
  #[napi(object)]
352
332
  pub struct RawPixelsWithDimensions {
353
- pub pixels: Vec<u8>,
333
+ pub pixels: Buffer,
354
334
  pub width: u32,
355
335
  pub height: u32,
356
336
  }
@@ -360,7 +340,7 @@ pub struct RawPixelsWithDimensions {
360
340
  pub fn sharp_to_raw(input_buffer: Buffer) -> Result<RawPixelsWithDimensions> {
361
341
  let (pixels, width, height) = image_utils::sharp_raw_pixels(&input_buffer)
362
342
  .map_err(|e| Error::from_reason(e))?;
363
- Ok(RawPixelsWithDimensions { pixels, width, height })
343
+ Ok(RawPixelsWithDimensions { pixels: pixels.into(), width, height })
364
344
  }
365
345
 
366
346
  #[cfg(not(test))]
@@ -373,8 +353,9 @@ pub fn sharp_metadata(input_buffer: Buffer) -> Result<SharpMetadata> {
373
353
 
374
354
  #[cfg(not(test))]
375
355
  #[napi]
376
- 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> {
377
357
  image_utils::rgb_to_png(&rgb_buffer, width, height)
358
+ .map(Buffer::from)
378
359
  .map_err(|e| Error::from_reason(e))
379
360
  }
380
361
 
@@ -383,27 +364,30 @@ pub fn rgb_to_png(rgb_buffer: Buffer, width: u32, height: u32) -> Result<Vec<u8>
383
364
  pub fn png_to_rgb(png_buffer: Buffer) -> Result<RawPixelsWithDimensions> {
384
365
  let (pixels, width, height) = image_utils::png_to_rgb(&png_buffer)
385
366
  .map_err(|e| Error::from_reason(e))?;
386
- Ok(RawPixelsWithDimensions { pixels, width, height })
367
+ Ok(RawPixelsWithDimensions { pixels: pixels.into(), width, height })
387
368
  }
388
369
 
389
370
  #[cfg(not(test))]
390
371
  #[napi]
391
- pub fn crop_and_reconstitute(png_buffer: Buffer) -> Result<Vec<u8>> {
372
+ pub fn crop_and_reconstitute(png_buffer: Buffer) -> Result<Buffer> {
392
373
  reconstitution::crop_and_reconstitute(&png_buffer)
374
+ .map(Buffer::from)
393
375
  .map_err(|e| Error::from_reason(e))
394
376
  }
395
377
 
396
378
  #[cfg(not(test))]
397
379
  #[napi]
398
- pub fn unstretch_nn(png_buffer: Buffer) -> Result<Vec<u8>> {
380
+ pub fn unstretch_nn(png_buffer: Buffer) -> Result<Buffer> {
399
381
  reconstitution::unstretch_nn(&png_buffer)
382
+ .map(Buffer::from)
400
383
  .map_err(|e| Error::from_reason(e))
401
384
  }
402
385
 
403
386
  #[cfg(not(test))]
404
387
  #[napi]
405
- pub fn extract_payload_from_png(png_buffer: Buffer) -> Result<Vec<u8>> {
388
+ pub fn extract_payload_from_png(png_buffer: Buffer) -> Result<Buffer> {
406
389
  png_utils::extract_payload_from_png(&png_buffer)
390
+ .map(Buffer::from)
407
391
  .map_err(|e| Error::from_reason(e))
408
392
  }
409
393
 
@@ -418,8 +402,9 @@ pub fn extract_file_list_from_pixels(png_buffer: Buffer) -> Result<String> {
418
402
 
419
403
  #[cfg(not(test))]
420
404
  #[napi]
421
- 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> {
422
406
  encoder::encode_to_wav(&buffer, compression_level)
407
+ .map(Buffer::from)
423
408
  .map_err(|e| Error::from_reason(e.to_string()))
424
409
  }
425
410
 
@@ -430,13 +415,14 @@ pub fn native_encode_wav_with_name_and_filelist(
430
415
  compression_level: i32,
431
416
  name: Option<String>,
432
417
  file_list_json: Option<String>,
433
- ) -> Result<Vec<u8>> {
418
+ ) -> Result<Buffer> {
434
419
  encoder::encode_to_wav_with_name_and_filelist(
435
420
  &buffer,
436
421
  compression_level,
437
422
  name.as_deref(),
438
423
  file_list_json.as_deref(),
439
424
  )
425
+ .map(Buffer::from)
440
426
  .map_err(|e| Error::from_reason(e.to_string()))
441
427
  }
442
428
 
@@ -449,7 +435,7 @@ pub fn native_encode_wav_with_encryption_name_and_filelist(
449
435
  encrypt_type: Option<String>,
450
436
  name: Option<String>,
451
437
  file_list_json: Option<String>,
452
- ) -> Result<Vec<u8>> {
438
+ ) -> Result<Buffer> {
453
439
  encoder::encode_to_wav_with_encryption_name_and_filelist(
454
440
  &buffer,
455
441
  compression_level,
@@ -458,26 +444,29 @@ pub fn native_encode_wav_with_encryption_name_and_filelist(
458
444
  name.as_deref(),
459
445
  file_list_json.as_deref(),
460
446
  )
447
+ .map(Buffer::from)
461
448
  .map_err(|e| Error::from_reason(e.to_string()))
462
449
  }
463
450
 
464
451
  #[cfg(not(test))]
465
452
  #[napi]
466
- pub fn native_decode_wav_payload(wav_buffer: Buffer) -> Result<Vec<u8>> {
453
+ pub fn native_decode_wav_payload(wav_buffer: Buffer) -> Result<Buffer> {
467
454
  encoder::decode_wav_payload(&wav_buffer)
455
+ .map(Buffer::from)
468
456
  .map_err(|e| Error::from_reason(e.to_string()))
469
457
  }
470
458
 
471
459
  #[cfg(not(test))]
472
460
  #[napi]
473
- pub fn native_bytes_to_wav(buffer: Buffer) -> Vec<u8> {
474
- audio::bytes_to_wav(&buffer)
461
+ pub fn native_bytes_to_wav(buffer: Buffer) -> Buffer {
462
+ audio::bytes_to_wav(&buffer).into()
475
463
  }
476
464
 
477
465
  #[cfg(not(test))]
478
466
  #[napi]
479
- pub fn native_wav_to_bytes(wav_buffer: Buffer) -> Result<Vec<u8>> {
467
+ pub fn native_wav_to_bytes(wav_buffer: Buffer) -> Result<Buffer> {
480
468
  audio::wav_to_bytes(&wav_buffer)
469
+ .map(Buffer::from)
481
470
  .map_err(|e| Error::from_reason(e))
482
471
  }
483
472
 
package/native/main.rs CHANGED
@@ -17,6 +17,7 @@ mod archive;
17
17
  mod streaming;
18
18
  mod streaming_decode;
19
19
  mod streaming_encode;
20
+ mod progress;
20
21
 
21
22
  use crate::encoder::ImageFormat;
22
23
  use std::path::PathBuf;
@@ -157,13 +158,16 @@ fn main() -> anyhow::Result<()> {
157
158
  .or_else(|| input.file_name().and_then(|n| n.to_str()));
158
159
 
159
160
  if is_dir && dict.is_none() {
160
- streaming_encode::encode_dir_to_png_encrypted(
161
+ streaming_encode::encode_dir_to_png_encrypted_with_progress(
161
162
  &input,
162
163
  &output,
163
164
  level,
164
165
  file_name,
165
166
  passphrase.as_deref(),
166
167
  Some(&encrypt),
168
+ Some(Box::new(|current, total, step| {
169
+ eprintln!("PROGRESS:{}:{}:{}", current, total, step);
170
+ })),
167
171
  )?;
168
172
  println!("(TAR archive, rXFL chunk embedded)");
169
173
  return Ok(());
@@ -187,6 +191,7 @@ fn main() -> anyhow::Result<()> {
187
191
  };
188
192
 
189
193
  let use_streaming = payload.len() > 64 * 1024 * 1024;
194
+ eprintln!("PROGRESS:50:100:encoding");
190
195
 
191
196
  if use_streaming {
192
197
  streaming::encode_to_png_file(
@@ -227,6 +232,7 @@ fn main() -> anyhow::Result<()> {
227
232
  }
228
233
 
229
234
  if file_list_json.is_some() {
235
+ eprintln!("PROGRESS:100:100:done");
230
236
  if is_dir {
231
237
  println!("(TAR archive, rXFL chunk embedded)");
232
238
  } else {
@@ -337,10 +343,18 @@ fn main() -> anyhow::Result<()> {
337
343
  && sig == [137, 80, 78, 71, 13, 10, 26, 10]
338
344
  });
339
345
 
340
- 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() {
341
347
  let out_dir = output.clone().unwrap_or_else(|| PathBuf::from("out.raw"));
342
- 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
+ ) {
343
356
  Ok(written) => {
357
+ eprintln!("PROGRESS:100:100:done");
344
358
  println!("Unpacked {} files (TAR)", written.len());
345
359
  return Ok(());
346
360
  }
@@ -351,6 +365,7 @@ fn main() -> anyhow::Result<()> {
351
365
  }
352
366
 
353
367
  let buf = read_all(&input)?;
368
+ eprintln!("PROGRESS:20:100:decompressing");
354
369
  let dict_bytes: Option<Vec<u8>> = match dict {
355
370
  Some(path) => Some(read_all(&path)?),
356
371
  None => None,
@@ -413,6 +428,7 @@ fn main() -> anyhow::Result<()> {
413
428
  let files_slice = file_list.as_ref().map(|v| v.as_slice());
414
429
 
415
430
  let written = packer::unpack_stream_to_dir(&mut reader, &out_dir, files_slice).map_err(|e| anyhow::anyhow!(e))?;
431
+ eprintln!("PROGRESS:100:100:done");
416
432
  println!("Unpacked {} files", written.len());
417
433
  } else {
418
434
  let is_png = buf.len() >= 8 && &buf[0..8] == &[137, 80, 78, 71, 13, 10, 26, 10];
@@ -511,6 +527,7 @@ fn main() -> anyhow::Result<()> {
511
527
  } else {
512
528
  write_all(&dest, &out_bytes)?;
513
529
  }
530
+ eprintln!("PROGRESS:100:100:done");
514
531
  }
515
532
  }
516
533
  Commands::Crc32 { input } => {
@@ -1,43 +1,142 @@
1
1
  use parking_lot::Mutex;
2
2
  use std::sync::Arc;
3
+ use std::time::Instant;
3
4
 
4
- pub struct ProgressBar {
5
+ #[derive(Clone, Debug)]
6
+ pub struct ProgressSnapshot {
7
+ pub current: u64,
8
+ pub total: u64,
9
+ pub percentage: f64,
10
+ pub elapsed_ms: u64,
11
+ pub eta_ms: Option<u64>,
12
+ pub speed_bytes_per_sec: f64,
13
+ pub step: String,
14
+ }
15
+
16
+ struct ProgressInner {
5
17
  total: u64,
6
- current: Arc<Mutex<u64>>,
7
- message: Arc<Mutex<String>>,
18
+ current: u64,
19
+ step: String,
20
+ start: Instant,
21
+ }
22
+
23
+ pub struct ProgressBar {
24
+ inner: Arc<Mutex<ProgressInner>>,
25
+ callback: Arc<Mutex<Option<Box<dyn Fn(ProgressSnapshot) + Send>>>>,
8
26
  }
9
27
 
10
28
  impl ProgressBar {
11
29
  pub fn new(total: u64) -> Self {
12
30
  Self {
13
- total,
14
- current: Arc::new(Mutex::new(0)),
15
- message: Arc::new(Mutex::new(String::new())),
31
+ inner: Arc::new(Mutex::new(ProgressInner {
32
+ total,
33
+ current: 0,
34
+ step: String::new(),
35
+ start: Instant::now(),
36
+ })),
37
+ callback: Arc::new(Mutex::new(None)),
38
+ }
39
+ }
40
+
41
+ pub fn with_callback<F: Fn(ProgressSnapshot) + Send + 'static>(total: u64, cb: F) -> Self {
42
+ Self {
43
+ inner: Arc::new(Mutex::new(ProgressInner {
44
+ total,
45
+ current: 0,
46
+ step: String::new(),
47
+ start: Instant::now(),
48
+ })),
49
+ callback: Arc::new(Mutex::new(Some(Box::new(cb)))),
16
50
  }
17
51
  }
18
52
 
19
53
  pub fn inc(&self, delta: u64) {
20
- let mut current = self.current.lock();
21
- *current += delta;
54
+ let snapshot = {
55
+ let mut inner = self.inner.lock();
56
+ inner.current += delta;
57
+ self.snapshot_inner(&inner)
58
+ };
59
+ self.emit(snapshot);
22
60
  }
23
61
 
24
62
  pub fn set(&self, value: u64) {
25
- let mut current = self.current.lock();
26
- *current = value;
63
+ let snapshot = {
64
+ let mut inner = self.inner.lock();
65
+ inner.current = value;
66
+ self.snapshot_inner(&inner)
67
+ };
68
+ self.emit(snapshot);
27
69
  }
28
70
 
29
- pub fn set_message(&self, msg: String) {
30
- let mut message = self.message.lock();
31
- *message = msg;
71
+ pub fn set_step(&self, step: &str) {
72
+ let snapshot = {
73
+ let mut inner = self.inner.lock();
74
+ inner.step = step.to_string();
75
+ self.snapshot_inner(&inner)
76
+ };
77
+ self.emit(snapshot);
32
78
  }
33
79
 
34
- pub fn get_progress(&self) -> (u64, u64) {
35
- let current = *self.current.lock();
36
- (current, self.total)
80
+ pub fn snapshot(&self) -> ProgressSnapshot {
81
+ let inner = self.inner.lock();
82
+ self.snapshot_inner(&inner)
83
+ }
84
+
85
+ fn snapshot_inner(&self, inner: &ProgressInner) -> ProgressSnapshot {
86
+ let elapsed = inner.start.elapsed();
87
+ let elapsed_ms = elapsed.as_millis() as u64;
88
+ let percentage = if inner.total > 0 {
89
+ (inner.current as f64 / inner.total as f64) * 100.0
90
+ } else {
91
+ 0.0
92
+ };
93
+
94
+ let elapsed_secs = elapsed.as_secs_f64();
95
+ let speed = if elapsed_secs > 0.01 {
96
+ inner.current as f64 / elapsed_secs
97
+ } else {
98
+ 0.0
99
+ };
100
+
101
+ let eta_ms = if speed > 0.0 && inner.current > 0 && inner.current < inner.total {
102
+ let remaining = inner.total - inner.current;
103
+ Some((remaining as f64 / speed * 1000.0) as u64)
104
+ } else {
105
+ None
106
+ };
107
+
108
+ ProgressSnapshot {
109
+ current: inner.current,
110
+ total: inner.total,
111
+ percentage,
112
+ elapsed_ms,
113
+ eta_ms,
114
+ speed_bytes_per_sec: speed,
115
+ step: inner.step.clone(),
116
+ }
117
+ }
118
+
119
+ fn emit(&self, snapshot: ProgressSnapshot) {
120
+ if let Some(ref cb) = *self.callback.lock() {
121
+ cb(snapshot);
122
+ }
37
123
  }
38
124
 
39
125
  pub fn finish(&self) {
40
- let mut current = self.current.lock();
41
- *current = self.total;
126
+ let snapshot = {
127
+ let mut inner = self.inner.lock();
128
+ inner.current = inner.total;
129
+ self.snapshot_inner(&inner)
130
+ };
131
+ self.emit(snapshot);
132
+ }
133
+
134
+ pub fn get_progress(&self) -> (u64, u64) {
135
+ let inner = self.inner.lock();
136
+ (inner.current, inner.total)
137
+ }
138
+
139
+ pub fn set_message(&self, msg: String) {
140
+ self.set_step(&msg);
42
141
  }
43
142
  }