harness_lock_core/
store.rs1use std::fs;
4use std::path::{Path, PathBuf};
5
6use crate::error::{Error, Result};
7use crate::hash::sha256_bytes;
8
9pub struct Store {
14 root: PathBuf,
15}
16
17impl Store {
18 pub fn new(root: impl Into<PathBuf>) -> Self {
19 Store { root: root.into() }
20 }
21
22 pub fn root(&self) -> &Path {
23 &self.root
24 }
25
26 pub fn put_bytes(&self, data: &[u8]) -> Result<String> {
28 fs::create_dir_all(&self.root)?;
29 let digest = sha256_bytes(data);
30 let target = self.path_for(&digest);
31 if !target.exists() {
32 fs::write(&target, data)?;
33 }
34 Ok(digest)
35 }
36
37 pub fn put_text(&self, text: &str) -> Result<String> {
38 self.put_bytes(text.as_bytes())
39 }
40
41 pub fn put_value(&self, v: &serde_json::Value) -> Result<String> {
42 self.put_text(&crate::hash::canon_json(v))
43 }
44
45 pub fn get_bytes(&self, reference: &str) -> Result<Vec<u8>> {
46 let p = self.path_for(reference);
47 if !p.exists() {
48 return Err(Error::Store(format!("blob not in store: {reference}")));
49 }
50 Ok(fs::read(p)?)
51 }
52
53 pub fn get_text(&self, reference: &str) -> Result<String> {
54 Ok(String::from_utf8_lossy(&self.get_bytes(reference)?).into_owned())
55 }
56
57 pub fn exists(&self, reference: &str) -> bool {
58 self.path_for(reference).exists()
59 }
60
61 pub fn count(&self) -> usize {
62 match fs::read_dir(&self.root) {
63 Ok(rd) => rd.filter_map(|e| e.ok()).filter(|e| e.path().is_file()).count(),
64 Err(_) => 0,
65 }
66 }
67
68 fn path_for(&self, reference: &str) -> PathBuf {
69 let hex = if let Some(idx) = reference.find(':') {
70 &reference[idx + 1..]
71 } else {
72 reference
73 };
74 self.root.join(hex)
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 #[test]
83 fn roundtrip() {
84 let dir = std::env::temp_dir().join(format!("hlock-store-{}", std::process::id()));
85 let s = Store::new(&dir);
86 let ref_ = s.put_text("hello").unwrap();
87 assert!(ref_.starts_with("sha256:"));
88 assert_eq!(s.get_text(&ref_).unwrap(), "hello");
89 assert!(s.exists(&ref_));
90 assert_eq!(s.count(), 1);
91 s.put_text("hello").unwrap();
92 assert_eq!(s.count(), 1);
93 let _ = fs::remove_dir_all(&dir);
94 }
95}