use std::fs::File;
use std::io::Read;
use std::ptr;
use std::path::Path;
use std::ffi::c_void;
#[allow(non_camel_case_types)]
type COMPRESSOR_HANDLE = *mut c_void;
const COMPRESS_ALGORITHM_XPRESS_HUFF: u32 = 4;
#[link(name = "cabinet")]
unsafe extern "system" {
fn CreateCompressor(
algorithm: u32,
allocationroutines: *const c_void,
compressorhandle: *mut COMPRESSOR_HANDLE,
) -> i32;
fn Compress(
compressorhandle: COMPRESSOR_HANDLE,
uncompresseddata: *const c_void,
uncompresseddatasize: usize,
compressedbuffer: *mut c_void,
compressedbuffersize: usize,
compresseddatasize: *mut usize,
) -> i32;
fn CloseCompressor(compressorhandle: COMPRESSOR_HANDLE) -> i32;
}
use crate::engine::wof::WofAlgorithm;
const SAMPLE_SIZE: usize = 256 * 1024;
const MIN_ESTIMATE_SIZE: u64 = 4096;
const MAX_TOTAL_SAMPLE_BYTES: u64 = 50 * 1024 * 1024;
const SMALL_FILE_SAMPLING_RATE: usize = 20;
const LARGE_FILE_THRESHOLD: u64 = 1024 * 1024;
fn map_algorithm(_algo: WofAlgorithm) -> u32 {
COMPRESS_ALGORITHM_XPRESS_HUFF
}
pub fn estimate_path(path: &str, algo: WofAlgorithm) -> u64 {
let p = Path::new(path);
if p.is_dir() {
let (_logical, estimated) = estimate_folder_size(path, algo);
estimated
} else if p.is_file() {
estimate_compressed_size(path, algo)
} else {
0
}
}
pub fn estimate_compressed_size(path: &str, algo: WofAlgorithm) -> u64 {
let mut file = match File::open(path) {
Ok(f) => f,
Err(_) => return 0,
};
let total_size = match file.metadata() {
Ok(m) => m.len(),
Err(_) => return 0,
};
if total_size < MIN_ESTIMATE_SIZE {
return total_size;
}
let sample_len = std::cmp::min(SAMPLE_SIZE, total_size as usize);
let mut sample = vec![0u8; sample_len];
let bytes_read = match file.read(&mut sample) {
Ok(n) if n > 0 => n,
_ => return total_size,
};
sample.truncate(bytes_read);
let compressed_size = compress_buffer(&sample, algo);
if compressed_size == 0 || compressed_size >= bytes_read {
return total_size;
}
let base_ratio = compressed_size as f64 / bytes_read as f64;
let adjusted_ratio = match algo {
WofAlgorithm::Xpress4K => base_ratio * 1.12, WofAlgorithm::Xpress8K => base_ratio * 1.05, WofAlgorithm::Xpress16K => base_ratio, WofAlgorithm::Lzx => base_ratio * 0.78, };
let estimated = (total_size as f64 * adjusted_ratio) as u64;
estimated.clamp(1, total_size)
}
fn compress_buffer(data: &[u8], algo: WofAlgorithm) -> usize {
if data.is_empty() {
return 0;
}
unsafe {
let mut compressor: COMPRESSOR_HANDLE = ptr::null_mut();
let win_algo = map_algorithm(algo);
if CreateCompressor(win_algo, ptr::null(), &mut compressor) == 0 {
return 0;
}
let mut compressed_size: usize = 0;
let _ = Compress(
compressor,
data.as_ptr() as *const _,
data.len(),
ptr::null_mut(),
0,
&mut compressed_size,
);
if compressed_size == 0 {
CloseCompressor(compressor);
return 0;
}
let mut output = vec![0u8; compressed_size];
let mut final_size: usize = 0;
let result = Compress(
compressor,
data.as_ptr() as *const _,
data.len(),
output.as_mut_ptr() as *mut _,
output.len(),
&mut final_size,
);
CloseCompressor(compressor);
if result == 0 {
return 0;
}
final_size
}
}
pub fn estimate_folder_size(path: &str, algo: WofAlgorithm) -> (u64, u64) {
let mut total_logical: u64 = 0;
let mut sampled_logical: u64 = 0;
let mut sampled_compressed: u64 = 0;
let mut file_count: usize = 0;
visit_dirs_sampling(
path,
algo,
&mut total_logical,
&mut sampled_logical,
&mut sampled_compressed,
&mut file_count
);
let estimated_total = if sampled_logical > 0 {
let ratio = sampled_compressed as f64 / sampled_logical as f64;
(total_logical as f64 * ratio) as u64
} else {
total_logical
};
(total_logical, estimated_total)
}
fn visit_dirs_sampling(
dir: &str,
algo: WofAlgorithm,
total_log: &mut u64,
samp_log: &mut u64,
samp_comp: &mut u64,
count: &mut usize
) {
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
if let Some(s) = path.to_str() {
visit_dirs_sampling(s, algo, total_log, samp_log, samp_comp, count);
}
} else if path.is_file() {
if let Ok(meta) = path.metadata() {
let len = meta.len();
*total_log += len;
*count += 1;
if *samp_log >= MAX_TOTAL_SAMPLE_BYTES {
continue;
}
let should_sample = if len > LARGE_FILE_THRESHOLD {
true
} else {
*count % SMALL_FILE_SAMPLING_RATE == 0
};
if should_sample {
if let Some(p_str) = path.to_str() {
let est = estimate_compressed_size(p_str, algo);
if est > 0 || len == 0 {
*samp_log += len;
*samp_comp += est;
}
}
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_map_algorithm() {
assert_eq!(map_algorithm(WofAlgorithm::Xpress4K), COMPRESS_ALGORITHM_XPRESS_HUFF);
assert_eq!(map_algorithm(WofAlgorithm::Xpress8K), COMPRESS_ALGORITHM_XPRESS_HUFF);
assert_eq!(map_algorithm(WofAlgorithm::Xpress16K), COMPRESS_ALGORITHM_XPRESS_HUFF);
assert_eq!(map_algorithm(WofAlgorithm::Lzx), COMPRESS_ALGORITHM_XPRESS_HUFF);
}
}