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