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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
//! The standard object store which should fit all needs.
use std::{cell::RefCell, ops::Deref};

use crate::Store;

/// This effectively acts like a handle but exists to be usable from the actual `crate::Handle` implementation which adds caches on top.
/// Each store is quickly cloned and contains thread-local state for shared packs.
pub struct Handle<S>
where
    S: Deref<Target = Store> + Clone,
{
    pub(crate) store: S,
    /// Defines what happens when there is no more indices to load.
    pub refresh: RefreshMode,
    /// The maximum recursion depth for resolving ref-delta base objects, that is objects referring to other objects within
    /// a pack.
    /// Recursive loops are possible only in purposefully crafted packs.
    /// This value doesn't have to be huge as in typical scenarios, these kind of objects are rare and chains supposedly are
    /// even more rare.
    pub max_recursion_depth: usize,

    /// If true, replacements will not be performed even if these are available.
    pub ignore_replacements: bool,

    pub(crate) token: Option<handle::Mode>,
    snapshot: RefCell<load_index::Snapshot>,
    packed_object_count: RefCell<Option<u64>>,
}

/// Decide what happens when all indices are loaded.
#[derive(Clone, Copy)]
pub enum RefreshMode {
    /// Check for new or changed pack indices (and pack data files) when the last known index is loaded.
    /// During runtime we will keep pack indices stable by never reusing them, however, there is the option for
    /// clearing internal caches which is likely to change pack ids and it will trigger unloading of packs as they are missing on disk.
    AfterAllIndicesLoaded,
    /// Use this if you expect a lot of missing objects that shouldn't trigger refreshes even after all packs are loaded.
    /// This comes at the risk of not learning that the packs have changed in the mean time.
    Never,
}

impl Default for RefreshMode {
    fn default() -> Self {
        RefreshMode::AfterAllIndicesLoaded
    }
}

impl RefreshMode {
    /// Set this refresh mode to never refresh.
    pub fn never(&mut self) {
        *self = RefreshMode::Never;
    }
}

///
pub mod find;

///
pub mod prefix;

mod header;

///
pub mod iter;

///
pub mod write;

///
pub mod init;

pub(crate) mod types;
pub use types::Metrics;

pub(crate) mod handle;

///
pub mod load_index;

///
pub mod verify;

mod load_one;

mod metrics;

mod access;

///
pub mod structure {
    use std::path::PathBuf;

    use crate::{store::load_index, types::IndexAndPacks, Store};

    /// A record of a structural element of an object database.
    #[derive(Debug, Clone, PartialEq, Eq)]
    #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
    pub enum Record {
        /// A loose object database.
        LooseObjectDatabase {
            /// The root of the object database.
            objects_directory: PathBuf,
            /// The amount of object files.
            num_objects: usize,
        },
        /// A pack index file
        Index {
            /// The location of the index file,
            path: PathBuf,
            /// Whether or not the index is mapped into memory.
            state: IndexState,
        },
        /// A multi-index file
        MultiIndex {
            /// The location of the multi-index file,
            path: PathBuf,
            /// Whether or not the index is mapped into memory.
            state: IndexState,
        },
        /// An empty slot was encountered, this is possibly happening as the ODB changes during query with
        /// a file being removed.
        Empty,
    }

    #[derive(Debug, Clone, PartialEq, Eq)]
    #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
    /// Possible stats of pack indices.
    pub enum IndexState {
        /// The index is active in memory because a mapping exists.
        Loaded,
        /// The index couldn't be unloaded as it was still in use, but that can happen another time.
        Disposable,
        /// The index isn't loaded/memory mapped.
        Unloaded,
    }

    impl Store {
        /// Return information about all files known to us as well as their loading state.
        ///
        /// Note that this call is expensive as it gathers additional information about loose object databases.
        /// Note that it may change as we collect information due to the highly volatile nature of the
        /// implementation. The likelihood of actual changes is low though as these still depend on something
        /// changing on disk and somebody reading at the same time.
        pub fn structure(&self) -> Result<Vec<Record>, load_index::Error> {
            let index = self.index.load();
            if !index.is_initialized() {
                self.consolidate_with_disk_state(true, false /*load one new index*/)?;
            }
            let index = self.index.load();
            let mut res: Vec<_> = index
                .loose_dbs
                .iter()
                .map(|db| Record::LooseObjectDatabase {
                    objects_directory: db.path.clone(),
                    num_objects: db.iter().count(),
                })
                .collect();

            for slot in index.slot_indices.iter().map(|idx| &self.files[*idx]) {
                let files = slot.files.load();
                let record = match &**files {
                    Some(index) => {
                        let state = if index.is_disposable() {
                            IndexState::Disposable
                        } else if index.index_is_loaded() {
                            IndexState::Loaded
                        } else {
                            IndexState::Unloaded
                        };
                        match index {
                            IndexAndPacks::Index(b) => Record::Index {
                                path: b.index.path().into(),
                                state,
                            },
                            IndexAndPacks::MultiIndex(b) => Record::MultiIndex {
                                path: b.multi_index.path().into(),
                                state,
                            },
                        }
                    }
                    None => Record::Empty,
                };
                res.push(record);
            }
            Ok(res)
        }
    }
}