roxify 1.13.4 → 1.13.6

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.
@@ -233,7 +233,7 @@ function spawnRustCLI(args, options) {
233
233
  runSpawn(cliPath);
234
234
  });
235
235
  }
236
- export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel = 3, passphrase, encryptType = 'aes', name, onProgress) {
236
+ export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel = 3, passphrase, encryptType = 'aes', name, ramBudgetMb, onProgress) {
237
237
  const cliPath = findRustBinary();
238
238
  if (!cliPath)
239
239
  throw new Error('Rust CLI binary not found');
@@ -250,10 +250,13 @@ export async function encodeWithRustCLI(inputPath, outputPath, compressionLevel
250
250
  args.push('--passphrase', passphrase);
251
251
  args.push('--encrypt', encryptType);
252
252
  }
253
+ if (typeof ramBudgetMb === 'number' && Number.isFinite(ramBudgetMb)) {
254
+ args.push('--ram-budget-mb', String(Math.max(1, Math.floor(ramBudgetMb))));
255
+ }
253
256
  args.push(inputPath, outputPath);
254
257
  await spawnRustCLI(args, { onProgress });
255
258
  }
256
- export async function decodeWithRustCLI(inputPath, outputPath, passphrase, files, dict, onProgress) {
259
+ export async function decodeWithRustCLI(inputPath, outputPath, passphrase, files, dict, ramBudgetMb, onProgress) {
257
260
  const args = ['decompress', inputPath, outputPath];
258
261
  if (passphrase)
259
262
  args.push('--passphrase', passphrase);
@@ -261,6 +264,9 @@ export async function decodeWithRustCLI(inputPath, outputPath, passphrase, files
261
264
  args.push('--files', JSON.stringify(files));
262
265
  if (dict)
263
266
  args.push('--dict', dict);
267
+ if (typeof ramBudgetMb === 'number' && Number.isFinite(ramBudgetMb)) {
268
+ args.push('--ram-budget-mb', String(Math.max(1, Math.floor(ramBudgetMb))));
269
+ }
264
270
  await spawnRustCLI(args, { onProgress });
265
271
  }
266
272
  export async function listWithRustCLI(inputPath) {
package/native/encoder.rs CHANGED
@@ -1,8 +1,11 @@
1
1
  use anyhow::Result;
2
2
 
3
+ use crate::png_chunk_writer::{write_chunked_idat_bytes, write_png_chunk};
4
+
3
5
  const MAGIC: &[u8] = b"ROX1";
4
6
  const PIXEL_MAGIC: &[u8] = b"PXL1";
5
7
  const PNG_HEADER: &[u8] = &[137, 80, 78, 71, 13, 10, 26, 10];
8
+ const HEADER_VERSION_V2: u8 = 2;
6
9
 
7
10
  const MARKER_START: [(u8, u8, u8); 3] = [(255, 0, 0), (0, 255, 0), (0, 0, 255)];
8
11
  const MARKER_END: [(u8, u8, u8); 3] = [(0, 0, 255), (0, 255, 0), (255, 0, 0)];
@@ -204,12 +207,12 @@ fn build_flat_pixel_buffer(
204
207
  }
205
208
 
206
209
  fn build_meta_pixel_with_name_and_filelist(payload: &[u8], name: Option<&str>, file_list: Option<&str>) -> Result<Vec<u8>> {
207
- let version = 1u8;
210
+ let version = HEADER_VERSION_V2;
208
211
  let name_bytes = name.map(|n| n.as_bytes()).unwrap_or(&[]);
209
212
  let name_len = name_bytes.len().min(255) as u8;
210
- let payload_len_bytes = (payload.len() as u32).to_be_bytes();
213
+ let payload_len_bytes = (payload.len() as u64).to_be_bytes();
211
214
 
212
- let mut result = Vec::with_capacity(1 + 1 + name_len as usize + 4 + payload.len() + 256);
215
+ let mut result = Vec::with_capacity(1 + 1 + name_len as usize + 8 + payload.len() + 256);
213
216
  result.push(version);
214
217
  result.push(name_len);
215
218
 
@@ -245,33 +248,18 @@ fn build_png(width: usize, height: usize, idat_data: &[u8], file_list: Option<&s
245
248
  ihdr_data[11] = 0;
246
249
  ihdr_data[12] = 0;
247
250
 
248
- write_chunk(&mut png, b"IHDR", &ihdr_data)?;
249
- write_chunk(&mut png, b"IDAT", idat_data)?;
251
+ write_png_chunk(&mut png, b"IHDR", &ihdr_data)?;
252
+ write_chunked_idat_bytes(&mut png, idat_data)?;
250
253
 
251
254
  if let Some(file_list_json) = file_list {
252
- write_chunk(&mut png, b"rXFL", file_list_json.as_bytes())?;
255
+ write_png_chunk(&mut png, b"rXFL", file_list_json.as_bytes())?;
253
256
  }
254
257
 
255
- write_chunk(&mut png, b"IEND", &[])?;
258
+ write_png_chunk(&mut png, b"IEND", &[])?;
256
259
 
257
260
  Ok(png)
258
261
  }
259
262
 
260
- fn write_chunk(out: &mut Vec<u8>, chunk_type: &[u8; 4], data: &[u8]) -> Result<()> {
261
- let len = data.len() as u32;
262
- out.extend_from_slice(&len.to_be_bytes());
263
- out.extend_from_slice(chunk_type);
264
- out.extend_from_slice(data);
265
-
266
- let mut hasher = crc32fast::Hasher::new();
267
- hasher.update(chunk_type);
268
- hasher.update(data);
269
- let crc = hasher.finalize();
270
-
271
- out.extend_from_slice(&crc.to_be_bytes());
272
- Ok(())
273
- }
274
-
275
263
  fn create_raw_deflate_from_rows(flat: &[u8], row_bytes: usize, height: usize) -> Vec<u8> {
276
264
  let stride = row_bytes + 1;
277
265
  let scanlines_total = height * stride;
@@ -0,0 +1,43 @@
1
+ use std::fs::File;
2
+ #[cfg(target_os = "linux")]
3
+ use std::os::fd::AsRawFd;
4
+
5
+ pub const INPUT_DROP_GRANULARITY: u64 = 8 * 1024 * 1024;
6
+
7
+ pub fn advise_file_sequential(file: &File) {
8
+ #[cfg(target_os = "linux")]
9
+ unsafe {
10
+ let _ = libc::posix_fadvise(file.as_raw_fd(), 0, 0, libc::POSIX_FADV_SEQUENTIAL);
11
+ }
12
+
13
+ #[cfg(not(target_os = "linux"))]
14
+ let _ = file;
15
+ }
16
+
17
+ pub fn advise_drop(file: &File, offset: u64, len: u64) {
18
+ if len == 0 {
19
+ return;
20
+ }
21
+
22
+ #[cfg(target_os = "linux")]
23
+ unsafe {
24
+ let _ = libc::posix_fadvise(
25
+ file.as_raw_fd(),
26
+ offset as libc::off_t,
27
+ len as libc::off_t,
28
+ libc::POSIX_FADV_DONTNEED,
29
+ );
30
+ }
31
+
32
+ #[cfg(not(target_os = "linux"))]
33
+ let _ = (file, offset, len);
34
+ }
35
+
36
+ pub fn sync_and_drop(file: &File, len: u64) {
37
+ if len < INPUT_DROP_GRANULARITY {
38
+ return;
39
+ }
40
+
41
+ let _ = file.sync_data();
42
+ advise_drop(file, 0, len);
43
+ }
@@ -0,0 +1,99 @@
1
+ use std::fs::{File, OpenOptions};
2
+ use std::io::{BufWriter, Write};
3
+ use std::path::Path;
4
+
5
+ /// Buffer size optimized for NTFS (larger = fewer syscalls)
6
+ #[cfg(windows)]
7
+ const NTFS_WRITE_BUFFER: usize = 4 * 1024 * 1024; // 4MB for Windows/NTFS
8
+ #[cfg(not(windows))]
9
+ const NTFS_WRITE_BUFFER: usize = 64 * 1024; // 64KB for Unix
10
+
11
+ /// Optimized file writer with large buffer for NTFS
12
+ pub struct OptimizedFileWriter {
13
+ writer: BufWriter<File>,
14
+ }
15
+
16
+ impl OptimizedFileWriter {
17
+ pub fn create(path: &Path) -> std::io::Result<Self> {
18
+ let file = OpenOptions::new()
19
+ .write(true)
20
+ .create(true)
21
+ .truncate(true)
22
+ .open(path)?;
23
+
24
+ Ok(Self {
25
+ writer: BufWriter::with_capacity(NTFS_WRITE_BUFFER, file),
26
+ })
27
+ }
28
+
29
+ pub fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
30
+ self.writer.write_all(buf)
31
+ }
32
+
33
+ pub fn flush(&mut self) -> std::io::Result<()> {
34
+ self.writer.flush()
35
+ }
36
+ }
37
+
38
+ /// Write file with optimized buffering for target filesystem
39
+ pub fn write_file_optimized(path: &Path, content: &[u8]) -> std::io::Result<()> {
40
+ let mut writer = OptimizedFileWriter::create(path)?;
41
+ writer.write_all(content)?;
42
+ writer.flush()?;
43
+ Ok(())
44
+ }
45
+
46
+ /// Batch write multiple files - keeps files open for better NTFS performance
47
+ pub fn write_files_batch(
48
+ base_dir: &Path,
49
+ files: &[(String, &[u8])],
50
+ ) -> Result<Vec<String>, String> {
51
+ let mut written = Vec::with_capacity(files.len());
52
+
53
+ for (rel_path, content) in files {
54
+ let safe_path = sanitize_path(rel_path);
55
+ let dest = base_dir.join(&safe_path);
56
+
57
+ if let Some(parent) = dest.parent() {
58
+ std::fs::create_dir_all(parent)
59
+ .map_err(|e| format!("Cannot create parent dir {:?}: {}", parent, e))?;
60
+ }
61
+
62
+ write_file_optimized(&dest, content)
63
+ .map_err(|e| format!("Cannot write {:?}: {}", dest, e))?;
64
+
65
+ written.push(safe_path.to_string_lossy().to_string());
66
+ }
67
+
68
+ Ok(written)
69
+ }
70
+
71
+ fn sanitize_path(path: &str) -> std::path::PathBuf {
72
+ let mut safe = std::path::PathBuf::new();
73
+ for comp in std::path::Path::new(path).components() {
74
+ if let std::path::Component::Normal(osstr) = comp {
75
+ safe.push(osstr);
76
+ }
77
+ }
78
+ safe
79
+ }
80
+
81
+ /// Pre-allocate file space on NTFS to reduce fragmentation
82
+ #[cfg(windows)]
83
+ pub fn preallocate_file(path: &Path, size: u64) -> std::io::Result<()> {
84
+ use std::os::windows::fs::FileExt;
85
+
86
+ let file = OpenOptions::new()
87
+ .write(true)
88
+ .create(true)
89
+ .open(path)?;
90
+
91
+ // Pre-allocate space
92
+ file.set_len(size)?;
93
+ Ok(())
94
+ }
95
+
96
+ #[cfg(not(windows))]
97
+ pub fn preallocate_file(_path: &Path, _size: u64) -> std::io::Result<()> {
98
+ Ok(())
99
+ }
package/native/lib.rs CHANGED
@@ -17,6 +17,8 @@ mod encoder;
17
17
  mod packer;
18
18
  mod crypto;
19
19
  mod png_utils;
20
+ mod png_chunk_writer;
21
+ mod io_advice;
20
22
  mod image_utils;
21
23
  mod audio;
22
24
  mod progress;