use crate::{
storage::Store,
string_cache::{CacheInstruction, CacheInstructionSet},
tape::{Instruction, InstructionSet, TapeMachine},
};
use std::{
fs::File,
io::{self, Read, Seek},
path::{Path, PathBuf},
};
pub struct Rotate {
file: Option<File>,
path: PathBuf,
path1: Option<PathBuf>,
max_len: u64,
}
impl Rotate {
pub fn new<P: AsRef<Path>>(path: P, max_len: u64) -> io::Result<Self> {
let file = File::options().append(true).create(true).open(&path)?;
let path1 = Self::path1(path.as_ref());
Ok(Self {
file: Some(file),
path: path.as_ref().to_owned(),
path1,
max_len,
})
}
pub fn file_mut(&mut self) -> io::Result<&mut File> {
self.file
.as_mut()
.ok_or_else(|| io::Error::new(io::ErrorKind::BrokenPipe, "file closed"))
}
pub fn do_needs_restart(&mut self) -> io::Result<bool> {
let max_len = self.max_len;
let file = self.file_mut()?;
if file.stream_position()? <= max_len {
return Ok(false);
}
self.file = None;
if let Some(path1) = self.path1.as_ref() {
std::fs::rename(&self.path, path1)?;
}
self.file = Some(File::create(&self.path)?);
Ok(true)
}
pub fn extract_logs<P: AsRef<Path>, W>(path: P, mut to: W) -> io::Result<()>
where
W: io::Write,
{
let path1 = Self::path1(path.as_ref());
let mut buf = vec![0; 4096];
let files = [
path1.and_then(|path| File::open(path).ok()),
File::open(path).ok(),
];
let files = files.into_iter().flatten();
for mut file in files {
loop {
let n = file.read(&mut buf)?;
let buf = &buf[..n];
if buf.is_empty() {
break;
}
to.write_all(buf)?;
to.flush()?;
}
}
Ok(())
}
fn path1(path: &Path) -> Option<PathBuf> {
path.to_str().map(|str| PathBuf::from(format!("{str}.1")))
}
}
impl TapeMachine<CacheInstructionSet> for Rotate {
fn needs_restart(&mut self) -> bool {
self.do_needs_restart().unwrap_or_default()
}
fn handle(&mut self, instruction: CacheInstruction) {
let Ok(file) = self.file_mut() else {
return;
};
let _ = Store::do_handle_cached(file, instruction);
}
}
impl TapeMachine<InstructionSet> for Rotate {
fn needs_restart(&mut self) -> bool {
self.do_needs_restart().unwrap_or_default()
}
fn handle(&mut self, instruction: Instruction) {
let Ok(file) = self.file_mut() else {
return;
};
let _ = Store::do_handle(file, instruction);
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::{
storage::Load,
tape::{FieldValue, Value},
};
#[test]
fn extract_logs() {
let dir = tempfile::TempDir::new().unwrap();
let path = dir.path().join("log");
let mut rotate = Rotate::new(&path, 8).unwrap();
TapeMachine::<InstructionSet>::handle(&mut rotate, Instruction::Restart);
assert!(!TapeMachine::<InstructionSet>::needs_restart(&mut rotate));
for i in 0..8 {
TapeMachine::<InstructionSet>::handle(
&mut rotate,
Instruction::AddValue(FieldValue {
name: "name",
value: Value::Integer(i),
}),
);
}
assert!(TapeMachine::<InstructionSet>::needs_restart(&mut rotate));
for i in 8..16 {
TapeMachine::<InstructionSet>::handle(
&mut rotate,
Instruction::AddValue(FieldValue {
name: "name",
value: Value::Integer(i),
}),
);
}
let load = dir.path().join("extracted");
Rotate::extract_logs(&path, File::create(&load).unwrap()).unwrap();
let mut load = Load::new(File::open(load).unwrap());
let next = load.fetch_one().unwrap();
match next {
Some(Instruction::Restart) => {}
unexpected => panic!("restart: unexpected instruction {unexpected:?}"),
}
for i in 0..16 {
let next = load.fetch_one().unwrap();
match next {
Some(Instruction::AddValue(FieldValue {
name,
value: Value::Integer(value),
})) => {
assert_eq!(name, "name");
assert_eq!(value, i);
}
unexpected => panic!("{i}: unexpected instruction {unexpected:?}"),
}
}
}
}