1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//! Backends work with underlying persistence layers.
//!
//! The `infinitree` crate only contains the bare minimum to implement
//! a filesystem-based tree.
//!
//! For more interesting backends, such as S3 and LRU caches, please
//! use the
//! [`infinitree-backends`](https://docs.rs/infinitree-backends)
//! crate.
use crate::object::{ObjectId, ReadObject, WriteObject};
use std::{io, sync::Arc};

mod directory;
pub use directory::Directory;

#[derive(thiserror::Error, Debug)]
pub enum BackendError {
    #[error("IO error: {source}")]
    Io {
        #[from]
        source: io::Error,
    },
    #[error("No object found")]
    NotFound { id: ObjectId },
    #[error("Can't create object")]
    Create,
    #[error("Backend Error: {source}")]
    Generic {
        #[from]
        source: anyhow::Error,
    },
}

pub type Result<T> = std::result::Result<T, BackendError>;

pub trait Backend: Send + Sync {
    fn write_object(&self, object: &WriteObject) -> Result<()>;
    fn read_object(&self, id: &ObjectId) -> Result<Arc<ReadObject>>;

    fn preload(&self, _objects: &[ObjectId]) -> Result<()> {
        Ok(())
    }

    fn delete(&self, _objects: &[ObjectId]) -> Result<()> {
        Ok(())
    }

    fn sync(&self) -> Result<()> {
        Ok(())
    }

    fn read_fresh(&self, id: &ObjectId) -> Result<Arc<ReadObject>> {
        self.read_object(id)
    }

    fn keep_warm(&self, _objects: &[ObjectId]) -> Result<()> {
        Ok(())
    }
}

#[cfg(any(test, bench, feature = "test"))]
pub mod test {
    use super::*;
    use std::{collections::HashMap, sync::Mutex};

    #[derive(Clone, Default)]
    pub struct InMemoryBackend(Arc<Mutex<HashMap<ObjectId, Arc<ReadObject>>>>);

    impl InMemoryBackend {
        pub fn new() -> Self {
            InMemoryBackend::default()
        }

        pub fn shared() -> Arc<Self> {
            Arc::new(InMemoryBackend::default())
        }
    }

    impl Backend for InMemoryBackend {
        fn write_object(&self, object: &WriteObject) -> Result<()> {
            self.0
                .lock()
                .unwrap()
                .insert(*object.id(), Arc::new(object.into()));
            Ok(())
        }

        fn read_object(&self, id: &ObjectId) -> Result<Arc<ReadObject>> {
            self.0
                .lock()
                .unwrap()
                .get(id)
                .ok_or(BackendError::NotFound { id: *id })
                .map(Arc::clone)
        }
    }

    #[derive(Clone, Default)]
    pub struct NullBackend(Arc<Mutex<usize>>);

    #[allow(clippy::len_without_is_empty)]
    impl NullBackend {
        pub fn len(&self) -> usize {
            *self.0.lock().unwrap()
        }
    }

    impl Backend for NullBackend {
        fn write_object(&self, _object: &WriteObject) -> Result<()> {
            *self.0.lock().unwrap() += 1;
            Ok(())
        }

        fn read_object(&self, _id: &ObjectId) -> Result<Arc<ReadObject>> {
            unimplemented!();
        }
    }
}