crate::ix!();
#[no_copy]
pub struct BufferedFile {
n_type: i32,
n_version: i32,
src: *mut libc::FILE,
n_src_pos: u64,
n_read_pos: u64,
n_read_limit: u64,
n_rewind: u64,
vch_buf: Vec<u8>,
}
impl Drop for BufferedFile {
fn drop(&mut self) {
self.fclose();
}
}
impl BufferedFile {
pub fn new(
file_in: *mut libc::FILE,
n_buf_size: u64,
n_rewind_in: u64,
n_type_in: i32,
n_version_in: i32,
) -> Self {
if n_rewind_in >= n_buf_size {
tracing::error!(
rewind = n_rewind_in,
buf_size = n_buf_size,
"Rewind limit must be less than buffer size"
);
panic!("Rewind limit must be less than buffer size");
}
Self {
n_type: n_type_in,
n_version: n_version_in,
src: file_in,
n_src_pos: 0,
n_read_pos: 0,
n_read_limit: u64::MAX,
n_rewind: n_rewind_in,
vch_buf: vec![0_u8; n_buf_size as usize],
}
}
pub fn fill(&mut self) -> bool {
let buf_len = self.vch_buf.len();
debug_assert!(buf_len > 0, "buffer length is never zero");
let pos = (self.n_src_pos % buf_len as u64) as usize;
let mut read = buf_len - pos;
let n_avail_u64 = buf_len as u64
- (self.n_src_pos - self.n_read_pos)
- self.n_rewind;
let n_avail = n_avail_u64 as usize;
if n_avail < read {
read = n_avail;
}
if read == 0 {
tracing::trace!(
pos,
n_avail,
"No space available to read more data into buffer"
);
return false;
}
let dest = unsafe { self.vch_buf.as_mut_ptr().add(pos) } as *mut libc::c_void;
let n_bytes = unsafe { libc::fread(dest, 1, read, self.src) };
if n_bytes == 0 {
let eof = unsafe { libc::feof(self.src) } != 0;
let msg = if eof {
"BufferedFile::fill: end of file"
} else {
"BufferedFile::fill: fread failed"
};
tracing::error!(?eof, "{}", msg);
panic!("{msg}");
}
self.n_src_pos += n_bytes as u64;
tracing::trace!(read = n_bytes, n_src_pos = self.n_src_pos, "Buffer filled");
true
}
pub fn fclose(&mut self) {
if !self.src.is_null() {
unsafe { libc::fclose(self.src) };
self.src = std::ptr::null_mut();
tracing::debug!("Underlying FILE* closed");
}
}
pub fn eof(&self) -> bool {
self.n_read_pos == self.n_src_pos && unsafe { libc::feof(self.src) } != 0
}
pub fn read(&mut self, mut pch: *mut u8, mut n_size: usize) {
if self.n_read_pos
.checked_add(n_size as u64)
.map_or(true, |n| n > self.n_read_limit)
{
tracing::error!(
attempted = n_size,
read_pos = self.n_read_pos,
limit = self.n_read_limit,
"Read attempted past buffer limit"
);
panic!("Read attempted past buffer limit");
}
let buf_len = self.vch_buf.len();
while n_size > 0 {
if self.n_read_pos == self.n_src_pos && !self.fill() {
tracing::error!("Unexpected EOF while reading");
panic!("BufferedFile::read: unexpected EOF");
}
let pos = (self.n_read_pos % buf_len as u64) as usize;
let mut n = n_size;
if n + pos > buf_len {
n = buf_len - pos;
}
if (self.n_read_pos + n as u64) > self.n_src_pos {
n = (self.n_src_pos - self.n_read_pos) as usize;
}
unsafe {
std::ptr::copy_nonoverlapping(
self.vch_buf.as_ptr().add(pos),
pch,
n,
);
pch = pch.add(n);
}
self.n_read_pos += n as u64;
n_size -= n;
}
tracing::trace!(bytes = n_size, "Read complete");
}
#[inline]
pub fn get_pos(&self) -> u64 {
self.n_read_pos
}
pub fn set_pos(&mut self, n_pos: u64) -> bool {
let bufsize = self.vch_buf.len() as u64;
let success = if n_pos + bufsize < self.n_src_pos {
self.n_read_pos = self.n_src_pos - bufsize;
false
} else if n_pos > self.n_src_pos {
self.n_read_pos = self.n_src_pos;
false
} else {
self.n_read_pos = n_pos;
true
};
tracing::debug!(
requested = n_pos,
new_pos = self.n_read_pos,
?success,
"set_pos completed"
);
success
}
pub fn set_limit(&mut self, n_pos: Option<u64>) -> bool {
let n_pos = n_pos.unwrap_or(u64::MAX);
if n_pos < self.n_read_pos {
tracing::warn!(
current_pos = self.n_read_pos,
new_limit = n_pos,
"Cannot set limit behind current read position"
);
false
} else {
self.n_read_limit = n_pos;
tracing::trace!(limit = n_pos, "Read limit updated");
true
}
}
pub fn find_byte(&mut self, ch: u8) {
let buf_len = self.vch_buf.len();
loop {
if self.n_read_pos == self.n_src_pos && !self.fill() {
tracing::error!(target = ?ch, "Byte not found before EOF");
panic!("BufferedFile::find_byte: reached EOF");
}
let idx = (self.n_read_pos % buf_len as u64) as usize;
if self.vch_buf[idx] == ch {
tracing::trace!(byte = ch, pos = self.n_read_pos, "Target byte located");
break;
}
self.n_read_pos += 1;
}
}
}