use super::DataWriterTrait;
use crate::{Blob, ByteRange};
use anyhow::{Result, ensure};
use async_trait::async_trait;
use std::{
fs::File,
io::{BufWriter, Seek, SeekFrom, Write},
path::Path,
};
use versatiles_derive::context;
pub struct DataWriterFile {
writer: BufWriter<File>,
}
impl DataWriterFile {
#[context("while creating file writer for path {:?}", path)]
pub fn from_path(path: &Path) -> Result<DataWriterFile> {
ensure!(path.is_absolute(), "path {path:?} must be absolute");
Ok(DataWriterFile {
writer: BufWriter::new(File::create(path)?),
})
}
}
#[async_trait]
impl DataWriterTrait for DataWriterFile {
#[context("while appending {} bytes to file", blob.len())]
fn append(&mut self, blob: &Blob) -> Result<ByteRange> {
let pos = self.writer.stream_position()?;
let len = self.writer.write(blob.as_slice())?;
Ok(ByteRange::new(pos, len as u64))
}
#[context("while writing {} bytes at start of file", blob.len())]
fn write_start(&mut self, blob: &Blob) -> Result<()> {
let pos = self.writer.stream_position()?;
self.writer.rewind()?;
self.writer.write_all(blob.as_slice())?;
self.writer.seek(SeekFrom::Start(pos))?;
Ok(())
}
#[context("while getting current write position")]
fn get_position(&mut self) -> Result<u64> {
Ok(self.writer.stream_position()?)
}
#[context("while setting write position to {}", position)]
fn set_position(&mut self, position: u64) -> Result<()> {
self.writer.seek(SeekFrom::Start(position))?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Blob;
use anyhow::Result;
use assert_fs::NamedTempFile;
use std::fs::File;
use std::io::Read;
#[test]
fn test_append_and_get_position() -> Result<()> {
let temp = NamedTempFile::new("test1")?;
let path = temp.path();
assert!(path.is_absolute());
let mut writer = DataWriterFile::from_path(path)?;
let data = Blob::from(vec![10, 20, 30]);
let range = writer.append(&data)?;
assert_eq!(range.to_string(), "[0..=2]");
assert_eq!(writer.get_position()?, 3);
let mut file = File::open(path)?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
assert_eq!(buf, data.as_slice());
Ok(())
}
#[test]
fn test_write_start_and_append() -> Result<()> {
let temp = NamedTempFile::new("test2")?;
let path = temp.path();
let mut writer = DataWriterFile::from_path(path)?;
let start = Blob::from(vec![1, 2, 3, 4]);
writer.write_start(&start)?;
assert_eq!(writer.get_position()?, 0);
let extra = Blob::from(vec![5, 6]);
let range2 = writer.append(&extra)?;
assert_eq!(range2.to_string(), "[0..=1]");
drop(writer);
let mut file = File::open(path)?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
assert_eq!(buf, &[5, 6, 3, 4]);
Ok(())
}
}