use std::collections::HashMap;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::path::Path;
struct OpenFile {
file: File,
len: u64,
}
#[derive(Default)]
pub struct Handles {
next: u32,
open: HashMap<u32, OpenFile>,
}
#[derive(Debug, thiserror::Error)]
pub enum HandleError {
#[error("no such handle: {0}")]
BadHandle(u32),
#[error("offset {offset} is past end of file ({len} bytes)")]
OffsetPastEnd {
offset: u64,
len: u64,
},
#[error("window of {0} bytes is too small to hold one character")]
WindowTooSmall(u64),
#[error(transparent)]
Io(#[from] std::io::Error),
}
pub struct Window {
pub text: String,
pub next_offset: u64,
}
impl Handles {
pub fn open(&mut self, path: &Path) -> Result<(u32, u64), HandleError> {
let file = File::open(path)?;
let len = file.metadata()?.len();
let handle = self.next;
self.next += 1;
self.open.insert(handle, OpenFile { file, len });
Ok((handle, len))
}
pub fn slice(&mut self, handle: u32, offset: u64, len: u64) -> Result<Window, HandleError> {
let f = self
.open
.get_mut(&handle)
.ok_or(HandleError::BadHandle(handle))?;
if offset > f.len {
return Err(HandleError::OffsetPastEnd { offset, len: f.len });
}
let want = len.min(f.len - offset) as usize;
let mut buf = vec![0u8; want];
f.file.seek(SeekFrom::Start(offset))?;
f.file.read_exact(&mut buf)?;
let valid = match std::str::from_utf8(&buf) {
Ok(_) => buf.len(),
Err(e) => e.valid_up_to(),
};
if valid == 0 && !buf.is_empty() {
return Err(HandleError::WindowTooSmall(len));
}
buf.truncate(valid);
Ok(Window {
text: String::from_utf8(buf).expect("truncated at a validated boundary"),
next_offset: offset + valid as u64,
})
}
}