use crate::{
node,
store::{
Store, StoreTags, StoreVersion, StoreWriter, TempCleaner, TempFile, TempLoader, Version,
},
Error, Result, SureNode,
};
use std::{
env,
fs::{self, File},
io::{self, BufRead, BufReader, BufWriter, Write},
path::{Path, PathBuf},
};
use weave::{
self, DeltaWriter, NamingConvention, NewWeave, PullParser, SimpleNaming,
};
pub struct WeaveStore {
naming: SimpleNaming,
}
impl WeaveStore {
pub fn new<P: AsRef<Path>>(path: P, base: &str, compressed: bool) -> WeaveStore {
WeaveStore {
naming: SimpleNaming::new(path, base, "dat", compressed),
}
}
}
impl Store for WeaveStore {
fn get_versions(&self) -> Result<Vec<StoreVersion>> {
let header = PullParser::new(&self.naming, 1)?.into_header();
let mut versions: Vec<_> = header
.deltas
.iter()
.map(|v| StoreVersion {
name: v.name.clone(),
time: v.time,
version: Version::Tagged(v.number.to_string()),
})
.collect();
versions.reverse();
Ok(versions)
}
fn load_iter(&self, version: Version) -> Result<Box<dyn Iterator<Item = Result<SureNode>>>> {
let last = weave::get_last_delta(&self.naming)?;
let last = match version {
Version::Latest => last,
Version::Prior => last - 1,
Version::Tagged(vers) => vers.parse()?,
};
Ok(Box::new(WeaveIter::new(&self.naming, last)?))
}
fn make_temp(&self) -> Result<Box<dyn TempFile + '_>> {
let (path, file) = self.naming.temp_file()?;
let cpath = path.clone();
Ok(Box::new(WeaveTemp {
parent: self,
path,
file: BufWriter::new(file),
cleaner: FileClean(cpath),
}))
}
fn make_new(&self, tags: &StoreTags) -> Result<Box<dyn StoreWriter + '_>> {
let itags = tags.iter().map(|(k, v)| (k.as_ref(), v.as_ref()));
match weave::get_last_delta(&self.naming) {
Ok(base) => {
let wv = DeltaWriter::new(&self.naming, itags, base)?;
Ok(Box::new(NewWeaveDelta { weave: wv }))
}
Err(_) => {
let wv = NewWeave::new(&self.naming, itags)?;
Ok(Box::new(NewWeaveWriter { weave: wv }))
}
}
}
}
struct WeaveTemp<'a> {
parent: &'a WeaveStore,
path: PathBuf,
file: BufWriter<File>,
cleaner: FileClean,
}
impl<'a> TempFile<'a> for WeaveTemp<'a> {
fn into_loader(self: Box<Self>) -> Result<Box<dyn TempLoader + 'a>> {
drop(self.file);
Ok(Box::new(WeaveTempLoader {
_parent: self.parent,
path: self.path,
cleaner: self.cleaner,
}))
}
fn into_cleaner(self: Box<Self>) -> Result<Box<dyn TempCleaner>> {
Ok(Box::new(self.cleaner))
}
}
impl<'a> Write for WeaveTemp<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.file.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.file.flush()
}
}
pub struct WeaveTempLoader<'a> {
_parent: &'a WeaveStore,
path: PathBuf,
cleaner: FileClean,
}
impl<'a> TempLoader for WeaveTempLoader<'a> {
fn new_loader(&self) -> Result<Box<dyn BufRead>> {
let read = BufReader::new(File::open(&self.path)?);
Ok(Box::new(read))
}
fn path_ref(&self) -> &Path {
&self.path
}
fn into_cleaner(self: Box<Self>) -> Result<Box<dyn TempCleaner>> {
Ok(Box::new(self.cleaner))
}
}
pub struct NewWeaveWriter<'a> {
weave: NewWeave<'a>,
}
impl<'a> StoreWriter<'a> for NewWeaveWriter<'a> {
fn commit(self: Box<Self>) -> Result<()> {
self.weave.close()?;
Ok(())
}
}
impl<'a> Write for NewWeaveWriter<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.weave.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.weave.flush()
}
}
pub struct NewWeaveDelta<'a> {
weave: DeltaWriter<'a>,
}
impl<'a> StoreWriter<'a> for NewWeaveDelta<'a> {
fn commit(self: Box<Self>) -> Result<()> {
self.weave.close()?;
Ok(())
}
}
impl<'a> Write for NewWeaveDelta<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.weave.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.weave.flush()
}
}
pub struct WeaveIter {
pull: Box<dyn Iterator<Item = Result<String>>>,
}
impl WeaveIter {
fn new(naming: &dyn NamingConvention, delta: usize) -> Result<WeaveIter> {
let mut pull = PullParser::new(naming, delta)?.filter_map(kept_text);
fixed(&mut pull, "asure-2.0")?;
fixed(&mut pull, "-----")?;
Ok(WeaveIter {
pull: Box::new(pull),
})
}
}
impl Iterator for WeaveIter {
type Item = Result<SureNode>;
fn next(&mut self) -> Option<Result<SureNode>> {
let line = match self.pull.next() {
Some(Err(e)) => return Some(Err(e)),
Some(Ok(line)) => line,
None => return None,
};
let line = line.as_bytes();
match line[0] {
b'd' => {
let (dname, datts) = node::decode_entity(&line[1..]);
Some(Ok(SureNode::Enter {
name: dname,
atts: datts,
}))
}
b'f' => {
let (fname, fatts) = node::decode_entity(&line[1..]);
Some(Ok(SureNode::File {
name: fname,
atts: fatts,
}))
}
b'-' => Some(Ok(SureNode::Sep)),
b'u' => Some(Ok(SureNode::Leave)),
ch => Some(Err(Error::InvalidSurefileChar(ch as char))),
}
}
}
fn kept_text(node: weave::Result<weave::Entry>) -> Option<Result<String>> {
match node {
Err(e) => Some(Err(e.into())),
Ok(weave::Entry::Plain { text, keep }) if keep => Some(Ok(text)),
_ => None,
}
}
fn fixed<I>(pull: &mut I, expect: &str) -> Result<()>
where
I: Iterator<Item = Result<String>>,
{
match pull.next() {
Some(Ok(line)) => {
if line == expect {
Ok(())
} else {
Err(Error::UnexpectedLine(line, expect.into()))
}
}
Some(Err(e)) => Err(e),
None => Err(Error::SureFileEof),
}
}
struct FileClean(PathBuf);
impl Drop for FileClean {
fn drop(&mut self) {
if env::var_os("RSURE_KEEP").is_none() {
let _ = fs::remove_file(&self.0);
}
}
}
impl TempCleaner for FileClean {}