Skip to main content

pijul_core/changestore/
filesystem.rs

1use super::*;
2use crate::change::{Change, ChangeFile};
3use crate::pristine::{Base32, ChangeId, Hash, Merkle, Vertex};
4use parking_lot::Mutex;
5use std::path::{Path, PathBuf};
6
7/// A file system change store.
8pub struct FileSystem {
9    change_cache: Mutex<lru_cache::LruCache<ChangeId, ChangeFile>>,
10    changes_dir: PathBuf,
11}
12
13impl Clone for FileSystem {
14    fn clone(&self) -> Self {
15        let len = self.change_cache.lock().capacity();
16        FileSystem {
17            changes_dir: self.changes_dir.clone(),
18            change_cache: Mutex::new(lru_cache::LruCache::new(len)),
19        }
20    }
21}
22
23#[derive(Debug, Error)]
24pub enum Error {
25    #[error(transparent)]
26    Io(#[from] std::io::Error),
27    #[error(transparent)]
28    Utf8(#[from] std::str::Utf8Error),
29    #[error(transparent)]
30    ChangeFile(#[from] crate::change::ChangeError),
31    #[error(transparent)]
32    Persist(#[from] tempfile::PersistError),
33}
34
35pub fn push_filename(changes_dir: &mut PathBuf, hash: &Hash) {
36    let h32 = hash.to_base32();
37    let (a, b) = h32.split_at(2);
38    changes_dir.push(a);
39    changes_dir.push(b);
40    changes_dir.set_extension("change");
41}
42
43pub fn push_tag_filename(changes_dir: &mut PathBuf, hash: &Merkle) {
44    let h32 = hash.to_base32();
45    let (a, b) = h32.split_at(2);
46    changes_dir.push(a);
47    changes_dir.push(b);
48    changes_dir.set_extension("tag");
49}
50
51pub fn pop_filename(changes_dir: &mut PathBuf) {
52    changes_dir.pop();
53    changes_dir.pop();
54}
55
56impl FileSystem {
57    pub fn filename(&self, hash: &Hash) -> PathBuf {
58        let mut path = self.changes_dir.clone();
59        push_filename(&mut path, hash);
60        path
61    }
62
63    pub fn tag_filename(&self, hash: &Merkle) -> PathBuf {
64        let mut path = self.changes_dir.clone();
65        push_tag_filename(&mut path, hash);
66        path
67    }
68
69    pub fn has_change(&self, hash: &Hash) -> bool {
70        std::fs::metadata(self.filename(hash)).is_ok()
71    }
72
73    /// Construct a `FileSystem`, starting from the root of the
74    /// repository (i.e. the parent of the `.pijul` directory).
75    pub fn from_root<P: AsRef<Path>>(root: P, cap: usize) -> Self {
76        let dot_pijul = root.as_ref().join(crate::DOT_DIR);
77        let changes_dir = dot_pijul.join("changes");
78        Self::from_changes(changes_dir, cap)
79    }
80
81    /// Construct a `FileSystem`, starting from the root of the
82    /// repository (i.e. the parent of the `.pijul` directory).
83    pub fn from_changes(changes_dir: PathBuf, cap: usize) -> Self {
84        std::fs::create_dir_all(&changes_dir).unwrap();
85        FileSystem {
86            changes_dir,
87            change_cache: Mutex::new(lru_cache::LruCache::new(cap)),
88        }
89    }
90
91    fn load<'a, F: Fn(ChangeId) -> Option<Hash>>(
92        &'a self,
93        hash: F,
94        change: ChangeId,
95    ) -> Result<
96        parking_lot::MutexGuard<'a, lru_cache::LruCache<ChangeId, ChangeFile>>,
97        crate::change::ChangeError,
98    > {
99        let mut change_cache = self.change_cache.lock();
100        if !change_cache.contains_key(&change) {
101            let h = hash(change).unwrap();
102            let path = self.filename(&h);
103            debug!("changefile: {:?}", path);
104            let p = crate::change::ChangeFile::open(h, path.to_str().unwrap())?;
105            debug!("patch done");
106            change_cache.insert(change, p);
107        }
108        Ok(change_cache)
109    }
110
111    pub fn save_from_buf(
112        &self,
113        buf: &[u8],
114        hash: &Hash,
115        change_id: Option<ChangeId>,
116    ) -> Result<(), crate::change::ChangeError> {
117        Change::check_from_buffer(buf, hash)?;
118        self.save_from_buf_unchecked(buf, hash, change_id)?;
119        Ok(())
120    }
121
122    pub fn save_from_buf_unchecked(
123        &self,
124        buf: &[u8],
125        hash: &Hash,
126        change_id: Option<ChangeId>,
127    ) -> Result<(), std::io::Error> {
128        let mut f = tempfile::NamedTempFile::new_in(&self.changes_dir)?;
129        let file_name = self.filename(hash);
130        use std::io::Write;
131        f.write_all(buf)?;
132        debug!("file_name = {:?}", file_name);
133        std::fs::create_dir_all(file_name.parent().unwrap())?;
134        f.persist(file_name)?;
135        if let Some(ref change_id) = change_id {
136            self.change_cache.lock().remove(change_id);
137        }
138        Ok(())
139    }
140}
141
142impl ChangeStore for FileSystem {
143    type Error = Error;
144    fn has_contents(&self, hash: Hash, change_id: Option<ChangeId>) -> bool {
145        if let Some(ref change_id) = change_id
146            && let Some(l) = self.change_cache.lock().get_mut(change_id)
147        {
148            return l.has_contents();
149        }
150        let path = self.filename(&hash);
151        match crate::change::ChangeFile::open(hash, path.to_str().unwrap()) {
152            Ok(p) => p.has_contents(),
153            _ => false,
154        }
155    }
156
157    fn get_header(&self, h: &Hash) -> Result<ChangeHeader, Self::Error> {
158        let path = self.filename(h);
159        let p = crate::change::ChangeFile::open(*h, path.to_str().unwrap())?;
160        Ok(p.hashed().header.clone())
161    }
162
163    fn get_contents<F: Fn(ChangeId) -> Option<Hash>>(
164        &self,
165        hash: F,
166        key: Vertex<ChangeId>,
167        buf: &mut [u8],
168    ) -> Result<usize, Self::Error> {
169        debug!("get_contents {:?}", key);
170        if key.end <= key.start || key.is_root() {
171            debug!("return 0");
172            return Ok(0);
173        }
174        assert_eq!(key.end - key.start, buf.len());
175        let mut cache = self.load(hash, key.change)?;
176        let p = cache.get_mut(&key.change).unwrap();
177        let n = p.read_contents(key.start.into(), buf)?;
178        debug!("get_contents {:?}", n);
179        Ok(n)
180    }
181    fn get_contents_ext(
182        &self,
183        key: Vertex<Option<Hash>>,
184        buf: &mut [u8],
185    ) -> Result<usize, Self::Error> {
186        if let Some(change) = key.change {
187            assert_eq!(key.end.us() - key.start.us(), buf.len());
188            if key.end <= key.start {
189                return Ok(0);
190            }
191            let path = self.filename(&change);
192            let mut p = crate::change::ChangeFile::open(change, path.to_str().unwrap())?;
193            let n = p.read_contents(key.start.into(), buf)?;
194            Ok(n)
195        } else {
196            Ok(0)
197        }
198    }
199    fn change_deletes_position<F: Fn(ChangeId) -> Option<Hash>>(
200        &self,
201        hash: F,
202        change: ChangeId,
203        pos: Position<Option<Hash>>,
204    ) -> Result<Vec<Hash>, Self::Error> {
205        let mut cache = self.load(hash, change)?;
206        let p = cache.get_mut(&change).unwrap();
207        let mut v = Vec::new();
208        for c in p.hashed().changes.iter() {
209            for c in c.iter() {
210                v.extend(c.deletes_pos(pos))
211            }
212        }
213        Ok(v)
214    }
215    fn save_change<
216        E: From<Self::Error> + From<ChangeError>,
217        F: FnOnce(&mut Change, &Hash) -> Result<(), E>,
218    >(
219        &self,
220        p: &mut Change,
221        ff: F,
222    ) -> Result<Hash, E> {
223        let mut f = match tempfile::NamedTempFile::new_in(&self.changes_dir) {
224            Ok(f) => f,
225            Err(e) => return Err(E::from(Error::from(e))),
226        };
227        let hash = {
228            let w = std::io::BufWriter::new(&mut f);
229            p.serialize(w, ff)?
230        };
231        let file_name = self.filename(&hash);
232        if let Err(e) = std::fs::create_dir_all(file_name.parent().unwrap()) {
233            return Err(E::from(Error::from(e)));
234        }
235        debug!("file_name = {:?}", file_name);
236        if let Err(e) = f.persist(file_name) {
237            return Err(E::from(Error::from(e)));
238        }
239        Ok(hash)
240    }
241    fn del_change(&self, hash: &Hash) -> Result<bool, Self::Error> {
242        let file_name = self.filename(hash);
243        debug!("file_name = {:?}", file_name);
244        let result = std::fs::remove_file(&file_name).is_ok();
245        std::fs::remove_dir(file_name.parent().unwrap()).unwrap_or(()); // fails silently if there are still changes with the same 2-letter prefix.
246        Ok(result)
247    }
248    fn get_change(&self, h: &Hash) -> Result<Change, Self::Error> {
249        let file_name = self.filename(h);
250        let file_name = file_name.to_str().unwrap();
251        debug!("file_name = {:?}", file_name);
252        Ok(Change::deserialize(file_name, Some(h))?)
253    }
254}