use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock};
use libpijul::change::{Change, ChangeError, ChangeHeader};
use libpijul::changestore::ChangeStore;
use libpijul::pristine::{Hash, Position};
use libpijul::{Base32, ChangeId, Merkle, Vertex};
use thiserror::Error;
#[derive(Clone)]
pub struct EagerChangeStore {
changes: Arc<RwLock<HashMap<Hash, Change>>>,
tags: Arc<RwLock<HashMap<Merkle, ChangeHeader>>>,
changes_dir: PathBuf,
}
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Utf8(#[from] std::str::Utf8Error),
#[error(transparent)]
Change(#[from] libpijul::change::ChangeError),
#[error(transparent)]
Bincode(#[from] bincode::Error),
#[error(transparent)]
Tag(#[from] libpijul::tag::TagError),
#[error(transparent)]
Persist(#[from] tempfile::PersistError),
}
impl EagerChangeStore {
pub fn from_root<P: AsRef<Path>>(root: P) -> Self {
EagerChangeStore {
changes: Arc::default(),
tags: Arc::default(),
changes_dir: root.as_ref().join(".pijul").join("changes"),
}
}
fn load(&self, h: &Hash) -> Result<(), Error> {
{
let r = self.changes.read().unwrap();
if let Some(_) = r.get(h) {
return Ok(());
}
}
let path = self.filename(h);
let file_name = path.to_str().unwrap();
let p = Change::deserialize(&file_name, Some(h))?;
self.changes.write().unwrap().insert(*h, p);
Ok(())
}
fn filename(&self, h: &Hash) -> PathBuf {
let mut path = self.changes_dir.clone();
let b32 = h.to_base32();
let (a, b) = b32.split_at(2);
path.push(a);
path.push(b);
path.set_extension("change");
path
}
}
impl ChangeStore for EagerChangeStore {
type Error = Error;
fn get_change(&self, h: &Hash) -> Result<Change, Self::Error> {
self.load(h)?;
let r = self.changes.read().unwrap();
Ok(r.get(h).unwrap().clone())
}
fn get_tag_header(&self, h: &Merkle) -> Result<ChangeHeader, Self::Error> {
let r = self.tags.read().unwrap();
if let Some(t) = r.get(&h) {
Ok(t.clone())
} else {
let mut path = self.changes_dir.clone();
let b32 = h.to_base32();
let (a, b) = b32.split_at(2);
path.push(a);
path.push(b);
path.set_extension("tag");
let mut p = libpijul::tag::OpenTagFile::open(&path, h)?;
let header = p.header()?;
self.tags.write().unwrap().insert(*h, header.clone());
Ok(header)
}
}
fn has_contents(&self, hash: Hash, _: Option<ChangeId>) -> bool {
match self.load(&hash) {
Ok(_) => {
let r = self.changes.read().unwrap();
match r.get(&hash) {
Some(c) => !c.contents.is_empty(),
None => false,
}
}
Err(_) => false,
}
}
fn get_contents<F: Fn(ChangeId) -> Option<Hash>>(
&self,
hash: F,
key: Vertex<ChangeId>,
buf: &mut [u8],
) -> Result<usize, Self::Error> {
if key.end <= key.start {
return Ok(0);
}
assert_eq!(buf.len(), key.end - key.start);
let h = hash(key.change).unwrap();
self.load(&h)?;
let r = self.changes.read().unwrap();
let p = r.get(&h).unwrap();
let start: usize = key.start.0 .0.try_into().unwrap();
let end: usize = key.end.0 .0.try_into().unwrap();
buf.clone_from_slice(&p.contents[start..end]);
Ok(end - start)
}
fn get_contents_ext(
&self,
key: Vertex<Option<Hash>>,
buf: &mut [u8],
) -> Result<usize, Self::Error> {
if let Some(change) = key.change {
if key.end <= key.start {
return Ok(0);
}
assert_eq!(key.end - key.start, buf.len());
self.load(&change)?;
let r = self.changes.read().unwrap();
let p = r.get(&change).unwrap();
let start: usize = key.start.0 .0.try_into().unwrap();
let end: usize = key.end.0 .0.try_into().unwrap();
buf.clone_from_slice(&p.contents[start..end]);
Ok(end - start)
} else {
Ok(0)
}
}
fn change_deletes_position<F: Fn(ChangeId) -> Option<Hash>>(
&self,
hash: F,
change: ChangeId,
pos: Position<Option<Hash>>,
) -> Result<Vec<Hash>, Self::Error> {
let r = self.changes.read().unwrap();
let change = r.get(&hash(change).unwrap()).unwrap();
let mut v = Vec::new();
for c in change.changes.iter() {
for c in c.iter() {
v.extend(c.deletes_pos(pos).into_iter())
}
}
Ok(v)
}
fn save_change<
E: From<Self::Error> + From<ChangeError>,
F: FnOnce(&mut Change, &Hash) -> Result<(), E>,
>(
&self,
p: &mut Change,
ff: F,
) -> Result<Hash, E> {
let mut f = match tempfile::NamedTempFile::new_in(&self.changes_dir) {
Ok(f) => f,
Err(e) => return Err(E::from(Error::from(e))),
};
let hash = {
let w = std::io::BufWriter::new(&mut f);
p.serialize(w, ff)?
};
let file_name = self.filename(&hash);
if let Err(e) = std::fs::create_dir_all(file_name.parent().unwrap()) {
return Err(E::from(Error::from(e)));
}
if let Err(e) = f.persist(file_name) {
return Err(E::from(Error::from(e)));
}
let mut w = self.changes.write().unwrap();
w.insert(hash, p.clone());
Ok(hash)
}
fn del_change(&self, h: &Hash) -> Result<bool, Self::Error> {
let file_name = self.filename(h);
std::fs::remove_file(&file_name).unwrap_or(());
std::fs::remove_dir(file_name.parent().unwrap()).unwrap_or(()); let mut w = self.changes.write().unwrap();
Ok(w.remove(h).is_some())
}
}