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/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "roxify_native"
3
- version = "1.13.1"
3
+ version = "1.13.3"
4
4
  edition = "2021"
5
5
  publish = false
6
6
 
@@ -51,26 +51,17 @@ pbkdf2 = "0.12"
51
51
  rand = "0.8"
52
52
  sha2 = "0.10"
53
53
  mimalloc = "0.1"
54
+ simd-adler32 = "0.3"
54
55
 
55
- wgpu = { version = "0.19", optional = true }
56
56
  memmap2 = "0.9"
57
57
  bytemuck = { version = "1.14", features = ["derive"] }
58
- # tokio is optional now; enable via the 'async' feature
59
58
  tokio = { version = "1", features = ["sync", "rt"], optional = true }
60
59
  parking_lot = "0.12"
61
- pollster = { version = "0.3", optional = true }
62
60
  libsais = { version = "0.2.0", default-features = false }
63
61
 
64
62
  [features]
65
- # default is intentionally empty so the crate compiles fast for local checks.
66
- # Enable 'gpu' to pull in the WGPU and pollster dependencies (heavy).
67
- # Enable 'async' to include tokio runtime (optional).
68
- # Example: `cargo build -p roxify_native --features gpu`
69
63
  default = []
70
-
71
- gpu = ["wgpu", "pollster"]
72
64
  async = ["tokio"]
73
- full = ["gpu", "async"]
74
65
 
75
66
  [profile.release]
76
67
  opt-level = 3
package/README.md CHANGED
@@ -57,151 +57,27 @@ The core compression and image-processing logic is written in Rust and exposed t
57
57
 
58
58
  ## Benchmarks
59
59
 
60
- All measurements were taken on Linux x64 (Intel i7-6700K @ 4.0 GHz, 32 GB RAM) with Node.js v20. Every tool uses its **maximum compression** setting: zip -9, gzip -9, 7z LZMA2 -mx=9, and Roxify Zstd level 19. Roxify produces a valid PNG or WAV file rather than a raw archive.
61
-
62
- ### BWT-ANS Native Compression (Rust, via N-API)
63
-
64
- Direct API calls through the native module (BWT → MTF → RLE0 → rANS, 1 MB blocks):
65
-
66
- | Dataset | Original | Compressed | Ratio | Encode | Decode | Enc Throughput | Dec Throughput |
67
- | ------- | -------- | ---------- | ----- | ------ | ------ | -------------- | -------------- |
68
- | Repetitive text 45 KB | 45 KB | 207 B | 0.5% | < 1 ms | < 1 ms | — | — |
69
- | Rust source 6 KB | 6 KB | 2.0 KB | 33.7% | < 1 ms | < 1 ms | — | — |
70
- | node_modules tar 175 MB | 175.5 MB | 38.4 MB | 21.9% | 9.68 s | 5.62 s | 18.1 MB/s | 31.2 MB/s |
71
- | Random 100 KB | 100 KB | 101 KB | 101.5% | < 1 ms | < 1 ms | — | — |
72
- | Zeros 100 KB | 100 KB | 51 B | 0.1% | < 1 ms | < 1 ms | — | — |
73
-
74
- > BWT-ANS achieves **21.9% on real-world node_modules** (175 MB), with **18 MB/s encode** and **31 MB/s decode** throughput. 100% lossless roundtrip verified on all datasets.
75
-
76
- ### Compression Ratio (Maximum Compression for All Tools)
77
-
78
- | Dataset | Original | zip -9 | gzip -9 | 7z LZMA2 -9 | Roxify PNG | Roxify WAV |
79
- | ----------- | -------- | --------------- | --------------- | --------------- | ------------------- | ------------------- |
80
- | Text 1 MB | 1.00 MB | 219 KB (21.4%) | 219 KB (21.4%) | 187 KB (18.3%) | **188 KB (18.3%)** | **187 KB (18.3%)** |
81
- | JSON 1 MB | 1.00 MB | 263 KB (25.7%) | 263 KB (25.7%) | 225 KB (22.0%) | **220 KB (21.5%)** | **219 KB (21.4%)** |
82
- | Binary 1 MB | 1.00 MB | 1.00 MB (100%) | 1.00 MB (100%) | 1.00 MB (100%) | 1.00 MB (100%) | 1.00 MB (100%) |
83
- | Mixed 5 MB | 5.00 MB | 2.45 MB (49.0%) | 2.45 MB (49.1%) | 2.33 MB (46.6%) | 2.38 MB (47.6%) | 2.38 MB (47.6%) |
84
- | Text 10 MB | 10.00 MB | 2.13 MB (21.3%) | 2.13 MB (21.3%) | 1.71 MB (17.1%) | **1.71 MB (17.1%)** | **1.70 MB (17.0%)** |
85
- | Mixed 10 MB | 10.00 MB | 4.90 MB (49.0%) | 4.90 MB (49.0%) | 4.65 MB (46.5%) | 4.73 MB (47.3%) | 4.73 MB (47.3%) |
86
-
87
- > **Roxify matches 7z LZMA2 ultra-compression on text** (18.3% for both at 1 MB) and **beats LZMA2 on JSON** (21.4% vs 22.0%). On mixed data, Roxify is within 1 percentage point of LZMA2 while producing a shareable PNG/WAV instead of an archive.
88
-
89
- ### Encode and Decode Speed (CLI)
90
-
91
- | Dataset | Tool | Encode | Decode | Enc Throughput | Dec Throughput |
92
- | ---------- | -------------- | ---------- | ---------- | -------------- | -------------- |
93
- | Text 1 MB | zip -9 | 112 ms | 36 ms | 8.9 MB/s | 27.6 MB/s |
94
- | | gzip -9 | 146 ms | 38 ms | 6.9 MB/s | 26.0 MB/s |
95
- | | 7z LZMA -9 | 303 ms | 21 ms | 3.3 MB/s | 46.6 MB/s |
96
- | | **Roxify PNG** | **859 ms** | **577 ms** | **1.2 MB/s** | **1.7 MB/s** |
97
- | | **Roxify WAV** | **794 ms** | **480 ms** | **1.3 MB/s** | **2.1 MB/s** |
98
- | JSON 1 MB | zip -9 | 79 ms | 20 ms | 12.7 MB/s | 50.5 MB/s |
99
- | | 7z LZMA -9 | 197 ms | 26 ms | 5.1 MB/s | 37.9 MB/s |
100
- | | **Roxify PNG** | **1.14 s** | **755 ms** | **0.9 MB/s** | **1.3 MB/s** |
101
- | | **Roxify WAV** | **1.49 s** | **518 ms** | **0.7 MB/s** | **1.9 MB/s** |
102
- | Text 10 MB | zip -9 | 1.21 s | 70 ms | 8.2 MB/s | 143.8 MB/s |
103
- | | 7z LZMA -9 | 5.05 s | 99 ms | 2.0 MB/s | 100.8 MB/s |
104
- | | **Roxify PNG** | **9.05 s** | **4.53 s** | **1.1 MB/s** | **2.2 MB/s** |
105
- | | **Roxify WAV** | **9.22 s** | **2.59 s** | **1.1 MB/s** | **3.9 MB/s** |
106
-
107
- > Roxify CLI includes Node.js startup overhead (~400 ms). In the JS API (below), the same operations are significantly faster. WAV decode is consistently faster than PNG decode due to simpler container parsing.
108
-
109
- ### JavaScript API Throughput
110
-
111
- Direct API calls (no CLI startup overhead):
112
-
113
- | Size | Container | Encode | Decode | Enc Throughput | Dec Throughput | Output | Ratio | Integrity |
114
- | ------ | --------- | ------ | ------- | -------------- | -------------- | --------- | ------ | --------- |
115
- | 1 KB | PNG | 9 ms | 12 ms | 0.1 MB/s | 0.1 MB/s | 1.14 KB | 114.3% | ✓ |
116
- | 10 KB | PNG | 18 ms | 34 ms | 0.5 MB/s | 0.3 MB/s | 10.32 KB | 103.2% | ✓ |
117
- | 100 KB | PNG | 52 ms | 109 ms | 1.9 MB/s | 0.9 MB/s | 100.52 KB | 100.5% | ✓ |
118
- | 500 KB | PNG | 339 ms | 541 ms | 1.4 MB/s | 0.9 MB/s | 502.64 KB | 100.5% | ✓ |
119
- | 1 MB | PNG | 875 ms | 1.24 s | 1.1 MB/s | 0.8 MB/s | 1.00 MB | 100.3% | ✓ |
120
- | 5 MB | PNG | 3.39 s | 4.12 s | 1.5 MB/s | 1.2 MB/s | 5.01 MB | 100.2% | ✓ |
121
- | 10 MB | PNG | 6.84 s | 12.28 s | 1.5 MB/s | 0.8 MB/s | 10.01 MB | 100.1% | ✓ |
122
- | 1 KB | WAV | 2 ms | 2 ms | 0.6 MB/s | 0.6 MB/s | 1.08 KB | 107.5% | ✓ |
123
- | 10 KB | WAV | 4 ms | 5 ms | 2.3 MB/s | 1.8 MB/s | 10.08 KB | 100.8% | ✓ |
124
- | 100 KB | WAV | 39 ms | 28 ms | 2.5 MB/s | 3.5 MB/s | 100.08 KB | 100.1% | ✓ |
125
- | 500 KB | WAV | 172 ms | 190 ms | 2.8 MB/s | 2.6 MB/s | 500.09 KB | 100.0% | ✓ |
126
- | 1 MB | WAV | 452 ms | 276 ms | 2.2 MB/s | 3.6 MB/s | 1.00 MB | 100.0% | ✓ |
127
- | 5 MB | WAV | 2.70 s | 1.65 s | 1.8 MB/s | 3.0 MB/s | 5.00 MB | 100.0% | ✓ |
128
- | 10 MB | WAV | 4.81 s | 2.56 s | 2.1 MB/s | 3.9 MB/s | 10.00 MB | 100.0% | ✓ |
129
-
130
- > WAV container is **2–4× faster** than PNG for decoding at large sizes, and produces slightly smaller output thanks to simpler framing.
131
-
132
- ### Reed-Solomon ECC Throughput
133
-
134
- | Size | Encode | Decode | Enc Throughput | Dec Throughput | Overhead |
135
- | ------ | ------ | ------ | -------------- | -------------- | -------- |
136
- | 1 KB | 6 ms | 4 ms | 0.2 MB/s | 0.2 MB/s | 125.7% |
137
- | 10 KB | 7 ms | 6 ms | 1.3 MB/s | 1.5 MB/s | 119.6% |
138
- | 100 KB | 49 ms | 45 ms | 2.0 MB/s | 2.1 MB/s | 118.8% |
139
- | 1 MB | 483 ms | 377 ms | 2.1 MB/s | 2.7 MB/s | 118.6% |
140
-
141
- ### Lossy-Resilient Encoding
142
-
143
- #### Robust Image (QR-code-style, block size 4×4)
144
-
145
- | Data Size | Encode Time | Output (PNG) |
146
- | --------- | ----------- | ------------ |
147
- | 32 B | 32 ms | 122 KB |
148
- | 128 B | 39 ms | 122 KB |
149
- | 512 B | 76 ms | 316 KB |
150
- | 1 KB | 139 ms | 508 KB |
151
- | 2 KB | 251 ms | 986 KB |
152
-
153
- #### Robust Audio (MFSK 8-channel, medium ECC)
154
-
155
- | Data Size | Encode | Decode | Output (WAV) | Integrity |
156
- | --------- | ------ | ------ | ------------ | --------- |
157
- | 10 B | 33 ms | 44 ms | 1.35 MB | ✓ |
158
- | 32 B | 19 ms | 31 ms | 1.35 MB | ✓ |
159
- | 64 B | 22 ms | 24 ms | 1.35 MB | ✓ |
160
- | 128 B | 21 ms | 28 ms | 1.35 MB | ✓ |
161
- | 256 B | 40 ms | 45 ms | 2.59 MB | ✓ |
162
-
163
- ### Data Integrity Verification
164
-
165
- All encode/decode roundtrips produce bit-exact output, verified by SHA-256:
166
-
167
- | Test Case | PNG | WAV |
168
- | ----------------------- | --- | --- |
169
- | Empty buffer (0 B) | ✓ | ✓ |
170
- | Single byte (1 B) | ✓ | ✓ |
171
- | All byte values (256 B) | ✓ | ✓ |
172
- | 1 KB text | ✓ | ✓ |
173
- | 100 KB random | ✓ | ✓ |
174
- | 1 MB random | ✓ | ✓ |
175
- | 5 MB random | ✓ | ✓ |
176
-
177
- **14 / 14 integrity tests passed** across both containers.
178
-
179
- ### Key Observations
180
-
181
- - **Roxify matches LZMA2 ultra-compression** on text data (18.3%) and **outperforms it on JSON** (21.4% vs 22.0%), while producing a standard PNG or WAV file instead of an archive.
182
- - **WAV container decode is 2–4× faster** than PNG decode at large sizes (3.9 MB/s vs 0.8 MB/s for 10 MB).
183
- - **WAV encode for 1 KB data completes in 2 ms** — well under the sub-second target.
184
- - **Lossy-resilient audio** encode/decode completes in under 50 ms for data up to 256 bytes, with full integrity.
185
- - **100% data integrity** across all sizes and containers — every byte is recovered exactly.
186
- - The CLI overhead (~400 ms Node.js startup) is amortized on larger inputs. For programmatic use, the JS API eliminates this entirely.
187
- - On incompressible (random) data, all tools converge to ~100% as expected. No compression algorithm can shrink truly random data.
188
-
189
- ### Methodology
190
-
191
- Benchmarks were generated using `test/benchmark-detailed.cjs`. Datasets consist of procedurally generated text, JSON, and random binary data. Each tool was invoked with its maximum compression setting:
192
-
193
- | Tool | Command / Setting |
194
- | -------- | --------------------------- |
195
- | zip | `zip -r -q -9` |
196
- | tar/gzip | `tar -cf - \| gzip -9` |
197
- | 7z | `7z a -mx=9` (LZMA2 ultra) |
198
- | Roxify | Zstd level 19, compact mode |
199
-
200
- To reproduce:
60
+ All measurements taken on Linux x64 (Intel i7-6700K @ 4.0 GHz, 32 GB RAM). Roxify uses its native Rust CLI (`roxify_native`) with streaming Zstd L3 + multi-threaded + LDM + window_log(30). ZIP uses `zip -r -q -9` (maximum compression).
201
61
 
202
- ```bash
203
- node test/benchmark-detailed.cjs
204
- ```
62
+ ### Real-world directory encoding: Roxify vs ZIP
63
+
64
+ | Dataset | Original | ZIP -9 | Roxify PNG | ZIP time | Roxify time | Speedup |
65
+ | --- | --- | --- | --- | --- | --- | --- |
66
+ | Test A (19 638 files, 177 MB) | 177 MB | 87.7 MB (49.6%) | 54.9 MB (31.0%) | 17.6 s | 1.2 s | 14.7x |
67
+ | Test B (3 936 files, 1.4 GB) | 1.4 GB | 513 MB (36.7%) | 409 MB (29.2%) | 1 min 46 s | 6.7 s | 15.9x |
68
+
69
+ ### Decompression
70
+
71
+ | Dataset | unzip | Roxify decode | Speedup |
72
+ | --- | --- | --- | --- |
73
+ | Test A (177 MB) | 2.4 s | 1.3 s | 1.8x |
74
+ | Test B (1.4 GB) | 8.4 s | 2.9 s | 2.9x |
75
+
76
+ Roxify produces a valid PNG image instead of a ZIP archive. On these real-world datasets it compresses 20-37% smaller than ZIP -9 while encoding 15x faster, thanks to multi-threaded Zstd with long-distance matching.
77
+
78
+ ### Data integrity
79
+
80
+ 100% lossless roundtrip verified by byte-exact diff on all datasets. Start and end markers verified in every output PNG.
205
81
 
206
82
  ---
207
83
 
@@ -661,6 +537,8 @@ Input --> Detect Format --> Demodulate/Read Blocks --> De-interleave --> RS ECC
661
537
  | `image_utils.rs` | Image resizing, pixel format conversion, metadata extraction |
662
538
  | `png_utils.rs` | Low-level PNG chunk read/write operations |
663
539
  | `progress.rs` | Progress tracking for long-running compression/decompression |
540
+ | `streaming_encode.rs` | Streaming directory-to-PNG encoder with real-time progress |
541
+ | `streaming_decode.rs` | Streaming PNG-to-directory decoder with real-time progress |
664
542
 
665
543
  ### TypeScript Modules
666
544
 
package/dist/cli.js CHANGED
@@ -20,7 +20,7 @@ async function loadJsEngine() {
20
20
  VFSIndexEntry: undefined,
21
21
  };
22
22
  }
23
- const VERSION = '1.13.1';
23
+ const VERSION = '1.13.2';
24
24
  function getDirectorySize(dirPath) {
25
25
  let totalSize = 0;
26
26
  try {
@@ -317,20 +317,17 @@ async function encodeCommand(args) {
317
317
  console.log(`Encoding to ${resolvedOutput} (Using native Rust encoder)\n`);
318
318
  const startTime = Date.now();
319
319
  const encodeBar = new cliProgress.SingleBar({ format: ' {bar} {percentage}% | {step} | {elapsed}s' }, cliProgress.Presets.shades_classic);
320
- let barValue = 0;
321
320
  encodeBar.start(100, 0, { step: 'Encoding', elapsed: '0' });
322
- const progressInterval = setInterval(() => {
323
- barValue = Math.min(barValue + 1, 99);
321
+ const encryptType = parsed.encrypt === 'xor' ? 'xor' : 'aes';
322
+ const fileName = basename(inputPaths[0]);
323
+ await encodeWithRustCLI(inputPaths.length === 1 ? resolvedInputs[0] : resolvedInputs[0], resolvedOutput, 19, parsed.passphrase, encryptType, fileName, (current, total, step) => {
324
+ const pct = total > 0 ? Math.floor((current / total) * 100) : 0;
324
325
  const elapsed = Math.floor((Date.now() - startTime) / 1000);
325
- encodeBar.update(barValue, {
326
- step: 'Encoding',
326
+ encodeBar.update(Math.min(pct, 99), {
327
+ step: step || 'Encoding',
327
328
  elapsed: String(elapsed),
328
329
  });
329
- }, 500);
330
- const encryptType = parsed.encrypt === 'xor' ? 'xor' : 'aes';
331
- const fileName = basename(inputPaths[0]);
332
- await encodeWithRustCLI(inputPaths.length === 1 ? resolvedInputs[0] : resolvedInputs[0], resolvedOutput, 19, parsed.passphrase, encryptType, fileName);
333
- clearInterval(progressInterval);
330
+ });
334
331
  const encodeTime = Date.now() - startTime;
335
332
  encodeBar.update(100, {
336
333
  step: 'done',
@@ -597,17 +594,15 @@ async function decodeCommand(args) {
597
594
  console.log('Decoding... (Using native Rust decoder)\n');
598
595
  const startTime = Date.now();
599
596
  const decodeBar = new cliProgress.SingleBar({ format: ' {bar} {percentage}% | {step} | {elapsed}s' }, cliProgress.Presets.shades_classic);
600
- let barValue = 0;
601
597
  decodeBar.start(100, 0, { step: 'Decoding', elapsed: '0' });
602
- const progressInterval = setInterval(() => {
603
- barValue = Math.min(barValue + 2, 99);
604
- decodeBar.update(barValue, {
605
- step: 'Decoding',
606
- elapsed: String(Math.floor((Date.now() - startTime) / 1000)),
598
+ await decodeWithRustCLI(resolvedInput, resolvedOutput, parsed.passphrase, parsed.files, parsed.dict, (current, total, step) => {
599
+ const pct = total > 0 ? Math.floor((current / total) * 100) : 0;
600
+ const elapsed = Math.floor((Date.now() - startTime) / 1000);
601
+ decodeBar.update(Math.min(pct, 99), {
602
+ step: step || 'Decoding',
603
+ elapsed: String(elapsed),
607
604
  });
608
- }, 300);
609
- await decodeWithRustCLI(resolvedInput, resolvedOutput, parsed.passphrase, parsed.files, parsed.dict);
610
- clearInterval(progressInterval);
605
+ });
611
606
  const decodeTime = Date.now() - startTime;
612
607
  decodeBar.update(100, { step: 'done', elapsed: String(Math.floor(decodeTime / 1000)) });
613
608
  decodeBar.stop();
package/dist/index.d.ts CHANGED
@@ -12,7 +12,7 @@ export * from './utils/optimization.js';
12
12
  export * from './utils/reconstitution.js';
13
13
  export * from './utils/robust-audio.js';
14
14
  export * from './utils/robust-image.js';
15
- export { decodeWithRustCLI, encodeWithRustCLI, havepassphraseWithRustCLI, isRustBinaryAvailable, listWithRustCLI, } from './utils/rust-cli-wrapper.js';
15
+ export { decodeWithRustCLI, encodeWithRustCLI, havepassphraseWithRustCLI, isRustBinaryAvailable, listWithRustCLI } from './utils/rust-cli-wrapper.js';
16
16
  export * from './utils/types.js';
17
17
  export * from './utils/zstd.js';
18
18
  export { packPaths, packPathsToParts, unpackBuffer } from './pack.js';
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ export * from './utils/optimization.js';
12
12
  export * from './utils/reconstitution.js';
13
13
  export * from './utils/robust-audio.js';
14
14
  export * from './utils/robust-image.js';
15
- export { decodeWithRustCLI, encodeWithRustCLI, havepassphraseWithRustCLI, isRustBinaryAvailable, listWithRustCLI, } from './utils/rust-cli-wrapper.js';
15
+ export { decodeWithRustCLI, encodeWithRustCLI, havepassphraseWithRustCLI, isRustBinaryAvailable, listWithRustCLI } from './utils/rust-cli-wrapper.js';
16
16
  export * from './utils/types.js';
17
17
  export * from './utils/zstd.js';
18
18
  export { packPaths, packPathsToParts, unpackBuffer } from './pack.js';
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1,7 +1,8 @@
1
1
  declare function findRustBinary(): string | null;
2
2
  export { findRustBinary };
3
3
  export declare function isRustBinaryAvailable(): boolean;
4
- export declare function encodeWithRustCLI(inputPath: string, outputPath: string, compressionLevel?: number, passphrase?: string, encryptType?: 'aes' | 'xor', name?: string): Promise<void>;
5
- export declare function decodeWithRustCLI(inputPath: string, outputPath: string, passphrase?: string, files?: string[], dict?: string): Promise<void>;
4
+ export type ProgressCallback = (current: number, total: number, step: string) => void;
5
+ export declare function encodeWithRustCLI(inputPath: string, outputPath: string, compressionLevel?: number, passphrase?: string, encryptType?: 'aes' | 'xor', name?: string, onProgress?: ProgressCallback): Promise<void>;
6
+ export declare function decodeWithRustCLI(inputPath: string, outputPath: string, passphrase?: string, files?: string[], dict?: string, onProgress?: ProgressCallback): Promise<void>;
6
7
  export declare function listWithRustCLI(inputPath: string): Promise<string>;
7
8
  export declare function havepassphraseWithRustCLI(inputPath: string): Promise<string>;
@@ -162,9 +162,12 @@ function spawnRustCLI(args, options) {
162
162
  let stdout = '';
163
163
  const runSpawn = (exePath) => {
164
164
  let proc;
165
+ const hasProgress = !!options?.onProgress;
165
166
  const stdio = options?.collectStdout
166
- ? ['pipe', 'pipe', 'inherit']
167
- : 'inherit';
167
+ ? ['pipe', 'pipe', hasProgress ? 'pipe' : 'inherit']
168
+ : hasProgress
169
+ ? ['pipe', 'inherit', 'pipe']
170
+ : 'inherit';
168
171
  try {
169
172
  proc = spawn(exePath, args, { stdio });
170
173
  }
@@ -184,6 +187,23 @@ function spawnRustCLI(args, options) {
184
187
  if (options?.collectStdout && proc.stdout) {
185
188
  proc.stdout.on('data', (chunk) => { stdout += chunk.toString(); });
186
189
  }
190
+ if (hasProgress && proc.stderr) {
191
+ let stderrBuf = '';
192
+ proc.stderr.on('data', (chunk) => {
193
+ stderrBuf += chunk.toString();
194
+ const lines = stderrBuf.split('\n');
195
+ stderrBuf = lines.pop() || '';
196
+ for (const line of lines) {
197
+ const match = line.match(/^PROGRESS:(\d+):(\d+):(.+)$/);
198
+ if (match) {
199
+ options.onProgress(Number(match[1]), Number(match[2]), match[3]);
200
+ }
201
+ else if (line.trim()) {
202
+ process.stderr.write(line + '\n');
203
+ }
204
+ }
205
+ });
206
+ }
187
207
  proc.on('error', (err) => {
188
208
  if (!triedExtract) {
189
209
  triedExtract = true;
@@ -213,7 +233,7 @@ function spawnRustCLI(args, options) {
213
233
  runSpawn(cliPath);
214
234
  });
215
235
  }
216
- export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel = 3, passphrase, encryptType = 'aes', name) {
236
+ export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel = 3, passphrase, encryptType = 'aes', name, onProgress) {
217
237
  const cliPath = findRustBinary();
218
238
  if (!cliPath)
219
239
  throw new Error('Rust CLI binary not found');
@@ -231,9 +251,9 @@ export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel
231
251
  args.push('--encrypt', encryptType);
232
252
  }
233
253
  args.push(inputPath, outputPath);
234
- await spawnRustCLI(args);
254
+ await spawnRustCLI(args, { onProgress });
235
255
  }
236
- export async function decodeWithRustCLI(inputPath, outputPath, passphrase, files, dict) {
256
+ export async function decodeWithRustCLI(inputPath, outputPath, passphrase, files, dict, onProgress) {
237
257
  const args = ['decompress', inputPath, outputPath];
238
258
  if (passphrase)
239
259
  args.push('--passphrase', passphrase);
@@ -241,7 +261,7 @@ export async function decodeWithRustCLI(inputPath, outputPath, passphrase, files
241
261
  args.push('--files', JSON.stringify(files));
242
262
  if (dict)
243
263
  args.push('--dict', dict);
244
- await spawnRustCLI(args);
264
+ await spawnRustCLI(args, { onProgress });
245
265
  }
246
266
  export async function listWithRustCLI(inputPath) {
247
267
  return spawnRustCLI(['list', inputPath], { collectStdout: true });
@@ -8,7 +8,7 @@ mod pool;
8
8
  mod hybrid;
9
9
 
10
10
  fn bench_roundtrip(name: &str, data: &[u8]) {
11
- let compressor = hybrid::HybridCompressor::new(false, 4);
11
+ let compressor = hybrid::HybridCompressor::new();
12
12
 
13
13
  let start = Instant::now();
14
14
  let (compressed, stats) = compressor.compress(data).unwrap();
package/native/core.rs CHANGED
@@ -84,54 +84,9 @@ pub fn crc32_bytes(buf: &[u8]) -> u32 {
84
84
  }
85
85
 
86
86
  pub fn adler32_bytes(buf: &[u8]) -> u32 {
87
- const MOD: u32 = 65521;
88
- const NMAX: usize = 5552;
89
-
90
- if buf.len() > 4 * 1024 * 1024 {
91
- return adler32_parallel(buf);
92
- }
93
-
94
- let mut a: u32 = 1;
95
- let mut b: u32 = 0;
96
-
97
- for chunk in buf.chunks(NMAX) {
98
- for &v in chunk {
99
- a += v as u32;
100
- b += a;
101
- }
102
- a %= MOD;
103
- b %= MOD;
104
- }
105
-
106
- (b << 16) | a
107
- }
108
-
109
- fn adler32_parallel(buf: &[u8]) -> u32 {
110
- use rayon::prelude::*;
111
- const MOD: u32 = 65521;
112
- const CHUNK: usize = 1024 * 1024;
113
-
114
- let chunks: Vec<&[u8]> = buf.chunks(CHUNK).collect();
115
- let partials: Vec<(u32, u32, usize)> = chunks.par_iter().map(|chunk| {
116
- let mut a: u32 = 0;
117
- let mut b: u32 = 0;
118
- for &v in *chunk {
119
- a += v as u32;
120
- b += a;
121
- }
122
- a %= MOD;
123
- b %= MOD;
124
- (a, b, chunk.len())
125
- }).collect();
126
-
127
- let mut a: u64 = 1;
128
- let mut b: u64 = 0;
129
- for (pa, pb, len) in partials {
130
- b = (b + pb as u64 + a * len as u64) % MOD as u64;
131
- a = (a + pa as u64) % MOD as u64;
132
- }
133
-
134
- ((b as u32) << 16) | (a as u32)
87
+ let mut hasher = simd_adler32::Adler32::new();
88
+ hasher.write(buf);
89
+ hasher.finish()
135
90
  }
136
91
 
137
92
  pub fn delta_encode_bytes(buf: &[u8]) -> Vec<u8> {
package/native/crypto.rs CHANGED
@@ -75,6 +75,11 @@ pub fn no_encryption(data: &[u8]) -> Vec<u8> {
75
75
  result
76
76
  }
77
77
 
78
+ pub fn no_encryption_in_place(mut data: Vec<u8>) -> Vec<u8> {
79
+ data.insert(0, ENC_NONE);
80
+ data
81
+ }
82
+
78
83
  pub fn decrypt_xor(data: &[u8], passphrase: &str) -> Result<Vec<u8>> {
79
84
  if data.is_empty() { return Err(anyhow!("Empty xor payload")); }
80
85
  if passphrase.is_empty() { return Err(anyhow!("Passphrase required")); }