1use crate::{
3 error::Result,
4 store::ObjectStore,
5 transfer::graph::{ObjectId, ObjectInfo, ObjectType},
6};
7
8#[derive(Debug, Clone, Default, PartialEq, Eq)]
9pub struct ObjectAvailabilityPlan {
10 pub have_objects: Vec<ObjectId>,
11 pub want_objects: Vec<ObjectId>,
12 pub partial_fetch_allowed: bool,
13}
14
15pub fn has_object(store: &impl ObjectStore, info: &ObjectInfo) -> Result<bool> {
16 match (&info.id, info.obj_type) {
17 (ObjectId::Hash(hash), ObjectType::Blob) => Ok(store.has_blob(hash)?),
18 (ObjectId::Hash(hash), ObjectType::Tree) => Ok(store.has_tree(hash)?),
19 (ObjectId::StateId(state_id), ObjectType::State) => Ok(store.has_state(state_id)?),
20 (ObjectId::StateAttachment { state, id, kind: _ }, ObjectType::StateAttachment) => {
21 Ok(store.get_state_attachment(state, id)?.is_some())
22 }
23 (ObjectId::Hash(_), ObjectType::Redaction) => Ok(false),
30 (ObjectId::Hash(_), ObjectType::Purge) => Ok(false),
33 (ObjectId::StateId(_), ObjectType::StateVisibility) => Ok(false),
37 (ObjectId::Hash(_), ObjectType::KeyBinding) => Ok(false),
40 _ => Ok(false),
41 }
42}
43
44pub fn plan_object_availability(
45 store: &impl ObjectStore,
46 objects: &[ObjectInfo],
47) -> Result<ObjectAvailabilityPlan> {
48 let mut plan = ObjectAvailabilityPlan::default();
49
50 for info in objects {
51 if has_object(store, info)? {
52 plan.have_objects.push(info.id.clone());
53 } else {
54 plan.want_objects.push(info.id.clone());
55 }
56 }
57
58 Ok(plan)
59}
60
61impl ObjectAvailabilityPlan {
62 pub fn with_partial_fetch_allowed(mut self, allowed: bool) -> Self {
63 self.partial_fetch_allowed = allowed;
64 self
65 }
66
67 pub fn is_complete(&self) -> bool {
68 self.want_objects.is_empty()
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use crate::{
75 object::{Blob, ContentHash, StateId, Tree},
76 store::{ObjectStore, Result as StoreResult, SidecarStore},
77 };
78
79 use super::*;
80
81 #[derive(Default)]
82 struct DummyStore {
83 blob: Option<ContentHash>,
84 state: Option<StateId>,
85 }
86
87 impl SidecarStore for DummyStore {}
88
89 impl ObjectStore for DummyStore {
90 fn get_blob(&self, _hash: &ContentHash) -> StoreResult<Option<Blob>> {
91 Ok(None)
92 }
93
94 fn put_blob(&self, _blob: &Blob) -> StoreResult<ContentHash> {
95 unreachable!("not used in test")
96 }
97
98 fn has_blob(&self, hash: &ContentHash) -> StoreResult<bool> {
99 Ok(self.blob == Some(*hash))
100 }
101
102 fn get_tree(&self, _hash: &ContentHash) -> StoreResult<Option<Tree>> {
103 Ok(None)
104 }
105
106 fn put_tree(&self, _tree: &Tree) -> StoreResult<ContentHash> {
107 unreachable!("not used in test")
108 }
109
110 fn has_tree(&self, _hash: &ContentHash) -> StoreResult<bool> {
111 Ok(false)
112 }
113
114 fn get_state(&self, _id: &StateId) -> StoreResult<Option<crate::object::State>> {
115 Ok(None)
116 }
117
118 fn put_state(&self, _state: &crate::object::State) -> StoreResult<()> {
119 unreachable!("not used in test")
120 }
121
122 fn has_state(&self, id: &StateId) -> StoreResult<bool> {
123 Ok(self.state == Some(*id))
124 }
125
126 fn list_states(&self) -> StoreResult<Vec<StateId>> {
127 Ok(vec![])
128 }
129
130 fn get_action(
131 &self,
132 _id: &crate::object::ActionId,
133 ) -> StoreResult<Option<crate::object::Action>> {
134 Ok(None)
135 }
136
137 fn put_action(
138 &self,
139 _action: &mut crate::object::Action,
140 ) -> StoreResult<crate::object::ActionId> {
141 unreachable!("not used in test")
142 }
143
144 fn list_actions(&self) -> StoreResult<Vec<crate::object::ActionId>> {
145 Ok(vec![])
146 }
147
148 fn list_blobs(&self) -> StoreResult<Vec<ContentHash>> {
149 Ok(vec![])
150 }
151
152 fn list_trees(&self) -> StoreResult<Vec<ContentHash>> {
153 Ok(vec![])
154 }
155 }
156
157 #[test]
158 fn test_plan_tracks_available_and_wanted_objects() {
159 let blob = Blob::new(b"hello".to_vec());
160 let blob_hash = blob.hash();
161 let store = DummyStore {
162 blob: Some(blob_hash),
163 state: None,
164 };
165 let missing_hash = ContentHash::from_bytes([7; 32]);
166 let objects = vec![
167 ObjectInfo {
168 id: ObjectId::Hash(blob_hash),
169 obj_type: ObjectType::Blob,
170 size: blob.size() as u64,
171 delta_base: None,
172 },
173 ObjectInfo {
174 id: ObjectId::Hash(missing_hash),
175 obj_type: ObjectType::Tree,
176 size: 0,
177 delta_base: None,
178 },
179 ];
180
181 let plan = plan_object_availability(&store, &objects).unwrap();
182
183 assert_eq!(plan.have_objects.len(), 1);
184 assert_eq!(plan.want_objects.len(), 1);
185 assert!(!plan.is_complete());
186 }
187
188 #[test]
189 fn missing_state_objects_are_requested() {
190 let store = DummyStore::default();
191 let state = StateId::from_bytes([9; 32]);
192 let objects = vec![ObjectInfo {
193 id: ObjectId::StateId(state),
194 obj_type: ObjectType::State,
195 size: 0,
196 delta_base: None,
197 }];
198
199 let plan = plan_object_availability(&store, &objects).unwrap();
200
201 assert!(plan.have_objects.is_empty());
202 assert_eq!(plan.want_objects, vec![ObjectId::StateId(state)]);
203 }
204
205 #[test]
206 fn immutable_state_objects_are_not_requested_when_present() {
207 let state = StateId::from_bytes([9; 32]);
208 let store = DummyStore {
209 state: Some(state),
210 ..DummyStore::default()
211 };
212 let objects = vec![ObjectInfo {
213 id: ObjectId::StateId(state),
214 obj_type: ObjectType::State,
215 size: 0,
216 delta_base: None,
217 }];
218
219 let plan = plan_object_availability(&store, &objects).unwrap();
220
221 assert_eq!(plan.have_objects, vec![ObjectId::StateId(state)]);
222 assert!(plan.want_objects.is_empty());
223 }
224
225 #[test]
226 fn test_partial_fetch_flag_helpers() {
227 let plan = ObjectAvailabilityPlan::default().with_partial_fetch_allowed(true);
228
229 assert!(plan.partial_fetch_allowed);
230 assert!(plan.is_complete());
231 }
232}