feat: add native zstd compression plugin and deduplicate shared compression/meta utilities
- Add zstd crate (v0.13) with native Rust compression engine (level 3) - Gate behind 'zstd' feature flag, fall back to program-based when disabled - Extract CompressionService::decompressing_reader/compressing_writer with zstd support - Extract MetaService::with_collector() to eliminate Arc<Mutex<Vec>> boilerplate - Extract read_with_bounds() helper for skip+read pattern - Add input validation for mutually exclusive --id and --tags flags - Add zstd round-trip tests
This commit is contained in:
@@ -26,3 +26,59 @@ pub fn stream_copy<R: std::io::Read + ?Sized>(
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reads content from a reader with offset and length bounds.
|
||||
///
|
||||
/// Skips `offset` bytes from the reader, then reads up to `length` bytes
|
||||
/// (or all remaining if `length` is 0). Uses PIPESIZE buffers throughout.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `reader` - The source reader positioned at the start.
|
||||
/// * `offset` - Number of bytes to skip before reading.
|
||||
/// * `length` - Maximum bytes to read (0 = read all remaining).
|
||||
/// * `content_len` - Total content size (used to cap skip/read amounts).
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Vec<u8>` containing the requested byte range.
|
||||
pub fn read_with_bounds<R: std::io::Read>(
|
||||
reader: &mut R,
|
||||
offset: u64,
|
||||
length: u64,
|
||||
content_len: u64,
|
||||
) -> std::io::Result<Vec<u8>> {
|
||||
// Skip offset bytes
|
||||
let skip = std::cmp::min(offset, content_len);
|
||||
let mut remaining = skip;
|
||||
let mut buf = [0u8; PIPESIZE];
|
||||
while remaining > 0 {
|
||||
let to_read = std::cmp::min(remaining, buf.len() as u64) as usize;
|
||||
match reader.read(&mut buf[..to_read]) {
|
||||
Ok(0) => break,
|
||||
Ok(n) => remaining -= n as u64,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
// Read bounded content
|
||||
let max_bytes = if length > 0 {
|
||||
std::cmp::min(length, content_len.saturating_sub(offset))
|
||||
} else {
|
||||
content_len.saturating_sub(offset)
|
||||
};
|
||||
let mut result = Vec::with_capacity(std::cmp::min(max_bytes, 64 * 1024) as usize);
|
||||
let mut bytes_read = 0u64;
|
||||
while bytes_read < max_bytes {
|
||||
let to_read = std::cmp::min(max_bytes - bytes_read, buf.len() as u64) as usize;
|
||||
match reader.read(&mut buf[..to_read]) {
|
||||
Ok(0) => break,
|
||||
Ok(n) => {
|
||||
result.extend_from_slice(&buf[..n]);
|
||||
bytes_read += n as u64;
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user