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(id), ObjectType::State) => Ok(store.has_state(id)?),
22 (ObjectId::Hash(_), ObjectType::Redaction) => Ok(false),
29 (ObjectId::ChangeId(_), ObjectType::StateVisibility) => Ok(false),
33 _ => Ok(false),
34 }
35}
36
37pub fn plan_object_availability(
38 store: &impl ObjectStore,
39 objects: &[ObjectInfo],
40) -> Result<ObjectAvailabilityPlan> {
41 let mut plan = ObjectAvailabilityPlan::default();
42
43 for info in objects {
44 if has_object(store, info)? {
45 plan.have_objects.push(info.id.clone());
46 plan.present_objects.push(info.id.clone());
47 } else {
48 plan.want_objects.push(info.id.clone());
49 plan.missing_objects.push(info.id.clone());
50 }
51 }
52
53 Ok(plan)
54}
55
56impl ObjectAvailabilityPlan {
57 pub fn with_partial_fetch_allowed(mut self, allowed: bool) -> Self {
58 self.partial_fetch_allowed = allowed;
59 self
60 }
61
62 pub fn is_complete(&self) -> bool {
63 self.want_objects.is_empty()
64 && self.missing_objects.is_empty()
65 && self.resumable_objects.is_empty()
66 && self.lazy_objects.is_empty()
67 }
68
69 pub fn has_partial_fetch_candidates(&self) -> bool {
70 !self.resumable_objects.is_empty() || !self.lazy_objects.is_empty()
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use objects::{
77 object::{Blob, ChangeId, ContentHash, Tree},
78 store::{ObjectStore, Result as StoreResult},
79 };
80
81 use super::*;
82
83 #[derive(Default)]
84 struct DummyStore {
85 blob: Option<ContentHash>,
86 }
87
88 impl ObjectStore for DummyStore {
89 fn get_blob(&self, _hash: &ContentHash) -> StoreResult<Option<Blob>> {
90 Ok(None)
91 }
92
93 fn put_blob(&self, _blob: &Blob) -> StoreResult<ContentHash> {
94 unreachable!("not used in test")
95 }
96
97 fn has_blob(&self, hash: &ContentHash) -> StoreResult<bool> {
98 Ok(self.blob == Some(*hash))
99 }
100
101 fn get_tree(&self, _hash: &ContentHash) -> StoreResult<Option<Tree>> {
102 Ok(None)
103 }
104
105 fn put_tree(&self, _tree: &Tree) -> StoreResult<ContentHash> {
106 unreachable!("not used in test")
107 }
108
109 fn has_tree(&self, _hash: &ContentHash) -> StoreResult<bool> {
110 Ok(false)
111 }
112
113 fn get_state(&self, _id: &ChangeId) -> StoreResult<Option<objects::object::State>> {
114 Ok(None)
115 }
116
117 fn put_state(&self, _state: &objects::object::State) -> StoreResult<()> {
118 unreachable!("not used in test")
119 }
120
121 fn has_state(&self, _id: &ChangeId) -> StoreResult<bool> {
122 Ok(false)
123 }
124
125 fn list_states(&self) -> StoreResult<Vec<ChangeId>> {
126 Ok(vec![])
127 }
128
129 fn get_action(
130 &self,
131 _id: &objects::object::ActionId,
132 ) -> StoreResult<Option<objects::object::Action>> {
133 Ok(None)
134 }
135
136 fn put_action(
137 &self,
138 _action: &mut objects::object::Action,
139 ) -> StoreResult<objects::object::ActionId> {
140 unreachable!("not used in test")
141 }
142
143 fn list_actions(&self) -> StoreResult<Vec<objects::object::ActionId>> {
144 Ok(vec![])
145 }
146
147 fn list_blobs(&self) -> StoreResult<Vec<ContentHash>> {
148 Ok(vec![])
149 }
150
151 fn list_trees(&self) -> StoreResult<Vec<ContentHash>> {
152 Ok(vec![])
153 }
154 }
155
156 #[test]
157 fn test_plan_tracks_present_and_missing_objects() {
158 let blob = Blob::new(b"hello".to_vec());
159 let blob_hash = blob.hash();
160 let store = DummyStore {
161 blob: Some(blob_hash),
162 };
163 let missing_hash = ContentHash::from_bytes([7; 32]);
164 let objects = vec![
165 ObjectInfo {
166 id: ObjectId::Hash(blob_hash),
167 obj_type: ObjectType::Blob,
168 size: blob.size() as u64,
169 delta_base: None,
170 },
171 ObjectInfo {
172 id: ObjectId::Hash(missing_hash),
173 obj_type: ObjectType::Tree,
174 size: 0,
175 delta_base: None,
176 },
177 ];
178
179 let plan = plan_object_availability(&store, &objects).unwrap();
180
181 assert_eq!(plan.have_objects.len(), 1);
182 assert_eq!(plan.want_objects.len(), 1);
183 assert_eq!(plan.present_objects.len(), 1);
184 assert_eq!(plan.missing_objects.len(), 1);
185 assert!(!plan.is_complete());
186 }
187
188 #[test]
189 fn test_partial_fetch_flag_helpers() {
190 let plan = ObjectAvailabilityPlan::default().with_partial_fetch_allowed(true);
191
192 assert!(plan.partial_fetch_allowed);
193 assert!(!plan.has_partial_fetch_candidates());
194 assert!(plan.is_complete());
195 }
196}