1use crate::store::{read_record, record};
18use crate::{Bound, Digest, Meta, Store, StoreError};
19use std::fs;
20use std::io;
21use std::path::{Path, PathBuf};
22use std::sync::atomic::{AtomicU64, Ordering};
23
24static LANDINGS: AtomicU64 = AtomicU64::new(0);
28
29pub struct Local {
31 root: PathBuf,
32}
33
34impl Local {
35 pub fn at(root: impl Into<PathBuf>) -> Result<Self, StoreError> {
37 let root = root.into();
38 for each in ["blobs", "names", "tmp"] {
39 fs::create_dir_all(root.join(each)).map_err(io_error)?;
40 }
41 Ok(Self { root })
42 }
43
44 fn blob(&self, digest: &Digest) -> PathBuf {
45 let (head, rest) = digest.path();
46 self.root.join("blobs").join(head).join(rest)
47 }
48
49 fn record(&self, name: &str) -> PathBuf {
50 let (head, rest) = Digest::of(name.as_bytes()).path();
51 self.root.join("names").join(head).join(rest)
52 }
53
54 fn land(&self, at: &Path, bytes: &[u8]) -> Result<(), StoreError> {
58 if let Some(directory) = at.parent() {
59 fs::create_dir_all(directory).map_err(io_error)?;
60 }
61 let landing = self.landing();
62 fs::write(&landing, bytes).map_err(io_error)?;
63 fs::rename(&landing, at).map_err(io_error)
64 }
65
66 fn landing(&self) -> PathBuf {
70 self.root.join("tmp").join(format!(
71 "{}-{}",
72 std::process::id(),
73 LANDINGS.fetch_add(1, Ordering::Relaxed)
74 ))
75 }
76}
77
78impl Store for Local {
79 fn put(&self, bytes: &[u8]) -> Result<Digest, StoreError> {
80 let digest = Digest::of(bytes);
81 let at = self.blob(&digest);
82 if !at.exists() {
84 self.land(&at, bytes)?;
85 }
86 Ok(digest)
87 }
88
89 fn get(&self, digest: &Digest) -> Result<Option<Vec<u8>>, StoreError> {
90 match fs::read(self.blob(digest)) {
91 Ok(bytes) => Ok(Some(bytes)),
92 Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None),
93 Err(e) => Err(io_error(e)),
94 }
95 }
96
97 fn bind(&self, name: &str, digest: &Digest, meta: Meta) -> Result<(), StoreError> {
98 self.land(&self.record(name), &record(name, digest, meta)?)
99 }
100
101 fn claim(&self, name: &str, digest: &Digest, meta: Meta) -> Result<bool, StoreError> {
102 let at = self.record(name);
103 if let Some(directory) = at.parent() {
104 fs::create_dir_all(directory).map_err(io_error)?;
105 }
106 let written = record(name, digest, meta)?;
107 let landing = self.landing();
108 fs::write(&landing, &written).map_err(io_error)?;
109 let taken = match fs::hard_link(&landing, &at) {
114 Ok(()) => true,
115 Err(e) if e.kind() == io::ErrorKind::AlreadyExists => false,
116 Err(e) => {
117 let _ = fs::remove_file(&landing);
118 return Err(io_error(e));
119 }
120 };
121 let _ = fs::remove_file(&landing);
124 Ok(taken)
125 }
126
127 fn resolve(&self, name: &str) -> Result<Option<Bound>, StoreError> {
128 match fs::read(self.record(name)) {
129 Ok(bytes) => read_record(&bytes).map(Some),
130 Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None),
131 Err(e) => Err(io_error(e)),
132 }
133 }
134
135 fn bound(&self) -> Result<Vec<Bound>, StoreError> {
136 let mut all = Vec::new();
137 let names = self.root.join("names");
138 for head in read_dir(&names)? {
139 for record in read_dir(&head)? {
140 all.push(read_record(&fs::read(record).map_err(io_error)?)?);
141 }
142 }
143 all.sort_by(|a, b| (a.when, &a.name).cmp(&(b.when, &b.name)));
146 Ok(all)
147 }
148}
149
150fn read_dir(at: &Path) -> Result<Vec<PathBuf>, StoreError> {
152 match fs::read_dir(at) {
153 Ok(entries) => entries
154 .map(|entry| entry.map(|entry| entry.path()).map_err(io_error))
155 .collect(),
156 Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(Vec::new()),
157 Err(e) => Err(io_error(e)),
158 }
159}
160
161fn io_error(e: io::Error) -> StoreError {
162 StoreError::Io(e.to_string())
163}