1use cid::Cid;
12use ferripfs_blockstore::Blockstore;
13use std::collections::HashSet;
14
15use crate::{PinError, PinInfo, PinMode, PinResult, PinStore};
16
17pub trait Pinner {
19 fn is_pinned(&self, cid: &Cid) -> PinResult<bool>;
21
22 fn is_pinned_with_mode(&self, cid: &Cid, mode: PinMode) -> PinResult<bool>;
24
25 fn pin(&mut self, cid: &Cid, mode: PinMode) -> PinResult<()>;
27
28 fn pin_with_name(&mut self, cid: &Cid, mode: PinMode, name: Option<String>) -> PinResult<()>;
30
31 fn unpin(&mut self, cid: &Cid, recursive: bool) -> PinResult<()>;
33
34 fn get_pin(&self, cid: &Cid) -> PinResult<Option<PinInfo>>;
36
37 fn list_pins(&self, mode: Option<PinMode>) -> PinResult<Vec<PinInfo>>;
39
40 fn update_pin(&mut self, old: &Cid, new: &Cid, unpin: bool) -> PinResult<()>;
42
43 fn verify(&self) -> PinResult<Vec<(Cid, String)>>;
45
46 fn pinned_cids(&self) -> PinResult<HashSet<Cid>>;
48}
49
50pub struct BlockstorePinner<'a, B: Blockstore> {
52 blockstore: &'a B,
53 store: PinStore,
54}
55
56impl<'a, B: Blockstore> BlockstorePinner<'a, B> {
57 pub fn new(blockstore: &'a B, store: PinStore) -> Self {
59 Self { blockstore, store }
60 }
61
62 pub fn store(&self) -> &PinStore {
64 &self.store
65 }
66
67 pub fn store_mut(&mut self) -> &mut PinStore {
69 &mut self.store
70 }
71
72 fn collect_refs(&self, cid: &Cid, refs: &mut HashSet<Cid>) -> PinResult<()> {
74 if refs.contains(cid) {
75 return Ok(());
76 }
77
78 let block = self
80 .blockstore
81 .get(cid)?
82 .ok_or_else(|| PinError::BlockNotFound(cid.to_string()))?;
83
84 if let Ok(links) = extract_links(block.data()) {
86 for link_cid in links {
87 refs.insert(link_cid);
88 self.collect_refs(&link_cid, refs)?;
89 }
90 }
91
92 Ok(())
93 }
94}
95
96impl<'a, B: Blockstore> Pinner for BlockstorePinner<'a, B> {
97 fn is_pinned(&self, cid: &Cid) -> PinResult<bool> {
98 Ok(self.store.is_pinned(cid))
99 }
100
101 fn is_pinned_with_mode(&self, cid: &Cid, mode: PinMode) -> PinResult<bool> {
102 Ok(self.store.is_pinned_with_mode(cid, mode))
103 }
104
105 fn pin(&mut self, cid: &Cid, mode: PinMode) -> PinResult<()> {
106 self.pin_with_name(cid, mode, None)
107 }
108
109 fn pin_with_name(&mut self, cid: &Cid, mode: PinMode, name: Option<String>) -> PinResult<()> {
110 if !self.blockstore.has(cid)? {
112 return Err(PinError::BlockNotFound(cid.to_string()));
113 }
114
115 match mode {
116 PinMode::Direct => {
117 self.store.add_direct(cid, name);
118 }
119 PinMode::Recursive => {
120 let mut refs = HashSet::new();
122 self.collect_refs(cid, &mut refs)?;
123
124 for ref_cid in &refs {
126 self.store.add_indirect(ref_cid, cid);
127 }
128
129 self.store.add_recursive(cid, name);
131 }
132 PinMode::Indirect => {
133 return Err(PinError::AlreadyPinned(
135 "Cannot create indirect pin directly".to_string(),
136 ));
137 }
138 }
139
140 Ok(())
141 }
142
143 fn unpin(&mut self, cid: &Cid, recursive: bool) -> PinResult<()> {
144 let pin_info = self.store.get(cid);
146
147 match pin_info {
148 Some(info) => match info.mode {
149 PinMode::Direct => {
150 self.store.remove_direct(cid);
151 Ok(())
152 }
153 PinMode::Recursive => {
154 if !recursive {
155 return Err(PinError::NotPinned(format!(
156 "{} is pinned recursively, use recursive unpin",
157 cid
158 )));
159 }
160
161 let mut refs = HashSet::new();
163 let _ = self.collect_refs(cid, &mut refs);
164
165 for ref_cid in &refs {
167 self.store.remove_indirect(ref_cid, cid);
168 }
169
170 self.store.remove_recursive(cid);
172 Ok(())
173 }
174 PinMode::Indirect => Err(PinError::CannotUnpinIndirect(cid.to_string())),
175 },
176 None => Err(PinError::NotPinned(cid.to_string())),
177 }
178 }
179
180 fn get_pin(&self, cid: &Cid) -> PinResult<Option<PinInfo>> {
181 Ok(self.store.get(cid))
182 }
183
184 fn list_pins(&self, mode: Option<PinMode>) -> PinResult<Vec<PinInfo>> {
185 Ok(self.store.list(mode))
186 }
187
188 fn update_pin(&mut self, old: &Cid, new: &Cid, unpin: bool) -> PinResult<()> {
189 let old_info = self
191 .store
192 .get(old)
193 .ok_or_else(|| PinError::NotPinned(old.to_string()))?;
194
195 if !self.blockstore.has(new)? {
197 return Err(PinError::BlockNotFound(new.to_string()));
198 }
199
200 self.pin_with_name(new, old_info.mode, old_info.name)?;
202
203 if unpin {
205 let recursive = old_info.mode == PinMode::Recursive;
206 self.unpin(old, recursive)?;
207 }
208
209 Ok(())
210 }
211
212 fn verify(&self) -> PinResult<Vec<(Cid, String)>> {
213 let mut errors = Vec::new();
214
215 for pin in self.store.list(None) {
217 if pin.mode == PinMode::Indirect {
218 continue; }
220
221 let cid =
222 Cid::try_from(pin.cid.as_str()).map_err(|e| PinError::CidParse(e.to_string()))?;
223
224 match self.blockstore.has(&cid) {
226 Ok(true) => {}
227 Ok(false) => {
228 errors.push((cid, "block not found".to_string()));
229 }
230 Err(e) => {
231 errors.push((cid, format!("error checking block: {}", e)));
232 }
233 }
234
235 if pin.mode == PinMode::Recursive {
237 let mut refs = HashSet::new();
238 if let Err(e) = self.collect_refs(&cid, &mut refs) {
239 errors.push((cid, format!("error collecting refs: {}", e)));
240 } else {
241 for ref_cid in refs {
242 match self.blockstore.has(&ref_cid) {
243 Ok(true) => {}
244 Ok(false) => {
245 errors.push((ref_cid, "referenced block not found".to_string()));
246 }
247 Err(e) => {
248 errors.push((ref_cid, format!("error checking block: {}", e)));
249 }
250 }
251 }
252 }
253 }
254 }
255
256 Ok(errors)
257 }
258
259 fn pinned_cids(&self) -> PinResult<HashSet<Cid>> {
260 let mut cids = HashSet::new();
261
262 for pin in self.store.list(None) {
263 let cid =
264 Cid::try_from(pin.cid.as_str()).map_err(|e| PinError::CidParse(e.to_string()))?;
265 cids.insert(cid);
266 }
267
268 Ok(cids)
269 }
270}
271
272fn extract_links(data: &[u8]) -> Result<Vec<Cid>, ()> {
274 use prost::Message;
275
276 #[derive(Clone, PartialEq, Message)]
278 struct PbLink {
279 #[prost(bytes, optional, tag = "1")]
280 hash: Option<Vec<u8>>,
281 #[prost(string, optional, tag = "2")]
282 name: Option<String>,
283 #[prost(uint64, optional, tag = "3")]
284 tsize: Option<u64>,
285 }
286
287 #[derive(Clone, PartialEq, Message)]
288 struct PbNode {
289 #[prost(message, repeated, tag = "2")]
290 links: Vec<PbLink>,
291 #[prost(bytes, optional, tag = "1")]
292 data: Option<Vec<u8>>,
293 }
294
295 let node = PbNode::decode(data).map_err(|_| ())?;
296
297 let mut cids = Vec::new();
298 for link in node.links {
299 if let Some(hash) = link.hash {
300 if let Ok(cid) = Cid::try_from(hash) {
301 cids.push(cid);
302 }
303 }
304 }
305
306 Ok(cids)
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312 use ferripfs_blockstore::{create_cid_v0, Block, FlatFsBlockstore};
313 use tempfile::tempdir;
314
315 fn create_test_block(data: &[u8]) -> Block {
316 let cid = create_cid_v0(data).unwrap();
317 Block::new(cid, data.to_vec())
318 }
319
320 #[test]
321 fn test_direct_pin() {
322 let dir = tempdir().unwrap();
323 let mut bs = FlatFsBlockstore::new_default(dir.path().join("blocks")).unwrap();
324
325 let block = create_test_block(b"test data");
327 let cid = *block.cid();
328 bs.put(block).unwrap();
329
330 let store = PinStore::new();
332 let mut pinner = BlockstorePinner::new(&bs, store);
333
334 pinner.pin(&cid, PinMode::Direct).unwrap();
336
337 assert!(pinner.is_pinned(&cid).unwrap());
339 assert!(pinner.is_pinned_with_mode(&cid, PinMode::Direct).unwrap());
340 assert!(!pinner
341 .is_pinned_with_mode(&cid, PinMode::Recursive)
342 .unwrap());
343
344 pinner.unpin(&cid, false).unwrap();
346 assert!(!pinner.is_pinned(&cid).unwrap());
347 }
348
349 #[test]
350 fn test_pin_with_name() {
351 let dir = tempdir().unwrap();
352 let mut bs = FlatFsBlockstore::new_default(dir.path().join("blocks")).unwrap();
353
354 let block = create_test_block(b"test data");
355 let cid = *block.cid();
356 bs.put(block).unwrap();
357
358 let store = PinStore::new();
359 let mut pinner = BlockstorePinner::new(&bs, store);
360
361 pinner
362 .pin_with_name(&cid, PinMode::Direct, Some("my-pin".to_string()))
363 .unwrap();
364
365 let info = pinner.get_pin(&cid).unwrap().unwrap();
366 assert_eq!(info.name, Some("my-pin".to_string()));
367 }
368
369 #[test]
370 fn test_list_pins() {
371 let dir = tempdir().unwrap();
372 let mut bs = FlatFsBlockstore::new_default(dir.path().join("blocks")).unwrap();
373
374 let block1 = create_test_block(b"data 1");
375 let block2 = create_test_block(b"data 2");
376 let cid1 = *block1.cid();
377 let cid2 = *block2.cid();
378 bs.put(block1).unwrap();
379 bs.put(block2).unwrap();
380
381 let store = PinStore::new();
382 let mut pinner = BlockstorePinner::new(&bs, store);
383
384 pinner.pin(&cid1, PinMode::Direct).unwrap();
385 pinner.pin(&cid2, PinMode::Direct).unwrap();
386
387 let all_pins = pinner.list_pins(None).unwrap();
388 assert_eq!(all_pins.len(), 2);
389
390 let direct_pins = pinner.list_pins(Some(PinMode::Direct)).unwrap();
391 assert_eq!(direct_pins.len(), 2);
392 }
393}