use std::{
collections::VecDeque,
fs::File,
io::{Read, Write},
};
pub struct Window {
elements: VecDeque<Vec<u8>>,
size: u16,
chunk_size: u16,
file: File,
}
impl Window {
pub fn new(size: u16, chunk_size: u16, file: File) -> Window {
Window {
elements: VecDeque::new(),
size,
chunk_size,
file,
}
}
pub fn fill(&mut self) -> anyhow::Result<bool> {
for _ in self.len()..self.size {
let mut chunk = vec![0; self.chunk_size as usize];
let size = self.file.read(&mut chunk)?;
if size != self.chunk_size as usize {
chunk.truncate(size);
self.elements.push_back(chunk);
return Ok(false);
}
self.elements.push_back(chunk);
}
Ok(true)
}
pub fn empty(&mut self) -> anyhow::Result<()> {
for data in &self.elements {
self.file.write_all(data)?;
}
self.elements.clear();
Ok(())
}
pub fn remove(&mut self, amount: u16) -> anyhow::Result<()> {
if amount > self.len() {
return Err(anyhow::anyhow!(
"amount cannot be larger than length of window"
));
}
drop(self.elements.drain(0..amount as usize));
Ok(())
}
pub fn add(&mut self, data: Vec<u8>) -> anyhow::Result<()> {
if self.len() == self.size {
return Err(anyhow::anyhow!("cannot add to a full window"));
}
self.elements.push_back(data);
Ok(())
}
pub fn get_elements(&self) -> &VecDeque<Vec<u8>> {
&self.elements
}
#[allow(dead_code)]
pub fn clear(&mut self) {
self.elements.clear();
}
pub fn len(&self) -> u16 {
self.elements.len() as u16
}
pub fn is_empty(&self) -> bool {
self.elements.is_empty()
}
pub fn is_full(&self) -> bool {
self.elements.len() as u16 == self.size
}
pub fn file_len(&self) -> anyhow::Result<u64> {
Ok(self.file.metadata()?.len())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::{
fs::{self, OpenOptions},
io::Write,
};
const DIR_NAME: &str = "target/test";
#[test]
fn fills_and_removes_from_window() {
const FILENAME: &str = "fills_and_removes_from_window.txt";
let mut file = initialize(FILENAME);
file.write_all(b"Hello, world!").unwrap();
file.flush().unwrap();
drop(file);
file = open(FILENAME);
let mut window = Window::new(2, 5, file);
window.fill().unwrap();
assert_eq!(window.elements.len(), 2);
assert_eq!(window.elements[0], b"Hello"[..]);
assert_eq!(window.elements[1], b", wor"[..]);
window.remove(1).unwrap();
assert_eq!(window.elements.len(), 1);
assert_eq!(window.elements[0], b", wor"[..]);
window.fill().unwrap();
assert_eq!(window.elements.len(), 2);
assert_eq!(window.elements[0], b", wor"[..]);
assert_eq!(window.elements[1], b"ld!"[..]);
clean(FILENAME);
}
#[test]
fn adds_to_and_empties_window() {
const FILENAME: &str = "adds_to_and_empties_window.txt";
let file = initialize(FILENAME);
let mut window = Window::new(3, 5, file);
window.add(b"Hello".to_vec()).unwrap();
assert_eq!(window.elements.len(), 1);
assert_eq!(window.elements[0], b"Hello"[..]);
window.add(b", wor".to_vec()).unwrap();
assert_eq!(window.elements.len(), 2);
assert_eq!(window.elements[0], b"Hello"[..]);
assert_eq!(window.elements[1], b", wor"[..]);
window.add(b"ld!".to_vec()).unwrap();
assert_eq!(window.elements.len(), 3);
assert_eq!(window.elements[0], b"Hello"[..]);
assert_eq!(window.elements[1], b", wor"[..]);
assert_eq!(window.elements[2], b"ld!"[..]);
window.empty().unwrap();
assert_eq!(window.elements.len(), 0);
let mut contents = Default::default();
File::read_to_string(
&mut File::open(DIR_NAME.to_string() + "/" + FILENAME).unwrap(),
&mut contents,
)
.unwrap();
assert_eq!(contents, "Hello, world!");
clean(FILENAME);
}
fn initialize(filename: &str) -> File {
let filename = DIR_NAME.to_string() + "/" + filename;
let _ = fs::create_dir_all(DIR_NAME);
if File::open(&filename).is_ok() {
fs::remove_file(&filename).unwrap();
}
OpenOptions::new()
.read(true)
.append(true)
.create(true)
.open(&filename)
.unwrap()
}
fn open(filename: &str) -> File {
let filename = DIR_NAME.to_string() + "/" + filename;
OpenOptions::new()
.read(true)
.append(true)
.create(true)
.open(filename)
.unwrap()
}
fn clean(filename: &str) {
let filename = DIR_NAME.to_string() + "/" + filename;
fs::remove_file(filename).unwrap();
if fs::remove_dir(DIR_NAME).is_err() {
}
}
}