#![allow(dead_code)]
use std::io::{self, Read};
pub type AsyncReaderResult<T> = Result<T, io::Error>;
pub struct BufferedAsyncReader {
#[cfg(not(target_arch = "wasm32"))]
file: std::fs::File,
buf_size: usize,
exhausted: bool,
bytes_read: u64,
}
impl BufferedAsyncReader {
#[cfg(not(target_arch = "wasm32"))]
pub fn new(path: &str, buf_size: usize) -> AsyncReaderResult<Self> {
let file = std::fs::File::open(path)?;
Ok(Self {
file,
buf_size: buf_size.max(1),
exhausted: false,
bytes_read: 0,
})
}
#[cfg(target_arch = "wasm32")]
pub fn new(_path: &str, buf_size: usize) -> AsyncReaderResult<Self> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"BufferedAsyncReader: filesystem not available on wasm32",
))
}
#[cfg(not(target_arch = "wasm32"))]
pub fn read_chunk(&mut self) -> Option<Vec<u8>> {
if self.exhausted {
return None;
}
let mut buf = vec![0u8; self.buf_size];
match self.file.read(&mut buf) {
Ok(0) => {
self.exhausted = true;
None
}
Ok(n) => {
buf.truncate(n);
self.bytes_read += n as u64;
Some(buf)
}
Err(_) => {
self.exhausted = true;
None
}
}
}
#[cfg(target_arch = "wasm32")]
pub fn read_chunk(&mut self) -> Option<Vec<u8>> {
None
}
#[must_use]
pub fn bytes_read(&self) -> u64 {
self.bytes_read
}
#[must_use]
pub fn is_exhausted(&self) -> bool {
self.exhausted
}
#[must_use]
pub fn buf_size(&self) -> usize {
self.buf_size
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_read_chunks_from_temp_file() {
let dir = std::env::temp_dir();
let path = dir.join("oximedia_io_async_reader_test.bin");
let data: Vec<u8> = (0u8..=255).collect(); std::fs::write(&path, &data).expect("write");
let path_str = path.to_string_lossy().to_string();
let mut reader = BufferedAsyncReader::new(&path_str, 64).expect("open");
let mut collected: Vec<u8> = Vec::new();
while let Some(chunk) = reader.read_chunk() {
collected.extend_from_slice(&chunk);
}
assert_eq!(collected, data);
assert!(reader.is_exhausted());
assert_eq!(reader.bytes_read(), 256);
let _ = std::fs::remove_file(&path);
}
#[test]
fn test_read_chunk_returns_none_after_eof() {
let dir = std::env::temp_dir();
let path = dir.join("oximedia_io_async_reader_eof_test.bin");
std::fs::write(&path, b"abc").expect("write");
let path_str = path.to_string_lossy().to_string();
let mut reader = BufferedAsyncReader::new(&path_str, 1024).expect("open");
let first = reader.read_chunk();
assert!(first.is_some());
assert_eq!(first.unwrap(), b"abc");
let second = reader.read_chunk();
assert!(second.is_none());
assert!(reader.is_exhausted());
assert!(reader.read_chunk().is_none());
let _ = std::fs::remove_file(&path);
}
#[test]
fn test_open_nonexistent_file_returns_error() {
let result = BufferedAsyncReader::new("/tmp/oximedia_nonexistent_file_xyz.bin", 1024);
assert!(result.is_err());
}
#[test]
fn test_empty_file() {
let dir = std::env::temp_dir();
let path = dir.join("oximedia_io_async_reader_empty.bin");
std::fs::write(&path, b"").expect("write");
let path_str = path.to_string_lossy().to_string();
let mut reader = BufferedAsyncReader::new(&path_str, 64).expect("open");
assert!(reader.read_chunk().is_none());
assert!(reader.is_exhausted());
assert_eq!(reader.bytes_read(), 0);
let _ = std::fs::remove_file(&path);
}
#[test]
fn test_buf_size_accessor() {
let dir = std::env::temp_dir();
let path = dir.join("oximedia_io_buf_size_test.bin");
std::fs::write(&path, b"data").expect("write");
let path_str = path.to_string_lossy().to_string();
let reader = BufferedAsyncReader::new(&path_str, 4096).expect("open");
assert_eq!(reader.buf_size(), 4096);
let _ = std::fs::remove_file(&path);
}
}