hyperon/metta/runner/pkg_mgmt/
managed_catalog.rs1
2use std::path::{Path, PathBuf};
3use std::collections::BTreeMap;
4use std::sync::Mutex;
5
6use git_catalog::{GitCatalog, ModuleGitLocation};
7use crate::metta::runner::*;
8use crate::metta::runner::pkg_mgmt::*;
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
46pub enum UpdateMode {
47 FetchIfMissing,
49 TryFetchIfOlderThan(u64),
52 TryFetchLatest,
55 FetchLatest,
57}
58
59impl UpdateMode {
60 pub fn promote_to(self, other: Self) -> Self {
62 match (&self, &other) {
63 (Self::FetchIfMissing, _) => other,
64 (Self::TryFetchIfOlderThan(_), Self::FetchIfMissing) => self,
65 (Self::TryFetchIfOlderThan(t_s), Self::TryFetchIfOlderThan(t_o)) => Self::TryFetchIfOlderThan((*t_s).min(*t_o)),
66 (Self::TryFetchIfOlderThan(_), _) => other,
67 (Self::TryFetchLatest, Self::FetchLatest) => Self::FetchLatest,
68 (Self::TryFetchLatest, _) => Self::TryFetchLatest,
69 _ => Self::FetchLatest
70 }
71 }
72}
73
74pub trait ManagedCatalog: ModuleCatalog {
75
76 fn clear_all(&self) -> Result<(), String>;
78
79 fn fetch(&self, descriptor: &ModuleDescriptor, update_mode: UpdateMode) -> Result<(), String>;
84
85 fn remove(&self, descriptor: &ModuleDescriptor) -> Result<(), String>;
87
88 fn fetch_newest_for_all(&self, update_mode: UpdateMode) -> Result<(), String> {
94 self.sync_toc(update_mode)?;
95 let iter = self.list_name_uid_pairs()
96 .ok_or_else(|| "managed catalog must support `list` method".to_string())?;
97 for (name, uid) in iter {
98 if let Some(desc) = self.lookup_newest_with_uid_and_version_req(&name, uid, None) {
99 self.fetch(&desc, update_mode)?;
100 }
101 }
102 Ok(())
103 }
104}
105
106#[derive(Debug)]
120pub struct LocalCatalog {
121 name: String,
122 upstream_catalogs: Vec<Box<dyn ModuleCatalog>>,
123 storage_dir: PathBuf,
124 local_toc: Mutex<LocalCatalogTOC>,
125}
126
127impl LocalCatalog {
128 pub fn new(caches_dir: &Path, name: &str) -> Result<Self, String> {
129 let storage_dir = caches_dir.join(name);
130 let local_toc = LocalCatalogTOC::build_from_dir(&storage_dir)?;
131
132 Ok(Self {
133 name: name.to_string(),
134 upstream_catalogs: vec![],
135 storage_dir,
136 local_toc: Mutex::new(local_toc),
137 })
138 }
139 pub fn push_upstream_catalog(&mut self, catalog: Box<dyn ModuleCatalog>) {
140 self.upstream_catalogs.push(catalog);
141 }
142 pub fn upstream_catalogs(&self) -> &[Box<dyn ModuleCatalog>] {
143 &self.upstream_catalogs[..]
144 }
145 fn first_upstream_git_catalog(&self) -> Option<&GitCatalog> {
148 for upstream in self.upstream_catalogs() {
149 if let Some(git_catalog) = upstream.downcast::<GitCatalog>() {
150 return Some(git_catalog)
151 }
152 }
153 None
154 }
155 pub(crate) fn loader_for_explicit_git_module(&self, mod_name: &str, update_mode: UpdateMode, location: &ModuleGitLocation) -> Result<Option<(Box<dyn ModuleLoader>, ModuleDescriptor)>, String> {
159 let descriptor = self.first_upstream_git_catalog()
160 .ok_or_else(|| format!("Catalog {} cannot pull modules from git", self.name))?
161 .register_mod(mod_name, None, location)?;
162 let loader = self.get_loader_with_explicit_refresh(&descriptor, update_mode)?;
163 Ok(Some((loader, descriptor)))
164 }
165 fn lookup_by_name_in_toc(&self, name: &str) -> Option<Vec<ModuleDescriptor>> {
166 let local_toc = self.local_toc.lock().unwrap();
167 local_toc.lookup_by_name(name)
168 }
169 fn add_to_toc(&self, descriptor: ModuleDescriptor) -> Result<(), String> {
171 let mut local_toc = self.local_toc.lock().unwrap();
172 local_toc.add_descriptor(descriptor)
173 }
174 fn list_toc(&self) -> Vec<ModuleDescriptor> {
175 let local_toc = self.local_toc.lock().unwrap();
176 local_toc.all_sorted_descriptors()
177 }
178 pub(crate) fn get_loader_with_explicit_refresh(&self, descriptor: &ModuleDescriptor, update_mode: UpdateMode) -> Result<Box<dyn ModuleLoader>, String> {
179
180 let mut upstream_loader = None;
182 for upstream in self.upstream_catalogs.iter() {
183 match upstream.get_loader(descriptor) {
184 Ok(loader) => {
185 upstream_loader = Some(loader);
186 break
187 },
188 Err(_) => {}
189 }
190 }
191 let upstream_loader = match upstream_loader {
192 Some(loader) => loader,
193 None => {
194 return Err(format!("Upstream Catalogs can no longer supply module \"{descriptor}\""));
198 }
199 };
200
201 let cache_dir_name = dir_name_from_descriptor(descriptor);
203 let local_cache_dir = self.storage_dir.join(cache_dir_name);
204
205 self.add_to_toc(descriptor.to_owned())?;
207
208 let wrapper_loader = LocalCatalogLoader {local_cache_dir, upstream_loader, update_mode};
210 Ok(Box::new(wrapper_loader))
211 }
212}
213
214impl ModuleCatalog for LocalCatalog {
215 fn display_name(&self) -> String {
216 self.name.clone()
217 }
218 fn lookup(&self, name: &str) -> Vec<ModuleDescriptor> {
219
220 if let Some(descriptors) = self.lookup_by_name_in_toc(name) {
222 return descriptors;
223 }
224
225 for upstream in self.upstream_catalogs.iter() {
228 let upstream_results = upstream.lookup(name);
229 if upstream_results.len() > 0 {
230 return upstream_results;
231 }
232 }
233
234 vec![]
236 }
237 fn get_loader(&self, descriptor: &ModuleDescriptor) -> Result<Box<dyn ModuleLoader>, String> {
238 self.get_loader_with_explicit_refresh(descriptor, UpdateMode::FetchIfMissing)
239 }
240 fn list<'a>(&'a self) -> Option<Box<dyn Iterator<Item=ModuleDescriptor> + 'a>> {
241 Some(Box::new(self.list_toc().into_iter()))
242 }
243 fn sync_toc(&self, update_mode: UpdateMode) -> Result<(), String> {
244 for upstream in self.upstream_catalogs.iter() {
245 upstream.sync_toc(update_mode)?;
246 }
247 Ok(())
248 }
249 fn as_managed(&self) -> Option<&dyn ManagedCatalog> {
250 Some(self)
251 }
252}
253
254#[derive(Debug)]
256struct LocalCatalogLoader {
257 local_cache_dir: PathBuf,
258 update_mode: UpdateMode,
259 upstream_loader: Box<dyn ModuleLoader>
260}
261
262impl ModuleLoader for LocalCatalogLoader {
263 fn prepare(&self, _local_dir: Option<&Path>, update_mode: UpdateMode) -> Result<Option<Box<dyn ModuleLoader>>, String> {
264 let update_mode = self.update_mode.promote_to(update_mode);
265 self.upstream_loader.prepare(Some(&self.local_cache_dir), update_mode)
266 }
267 fn load(&self, _context: &mut RunContext) -> Result<(), String> {
268 unreachable!() }
270}
271
272impl ManagedCatalog for LocalCatalog {
273 fn clear_all(&self) -> Result<(), String> {
274 if self.storage_dir.is_dir() {
275 std::fs::remove_dir_all(&self.storage_dir).map_err(|e| e.to_string())?;
276 }
277 let mut local_toc = self.local_toc.lock().unwrap();
278 *local_toc = LocalCatalogTOC::build_from_dir(&self.storage_dir)?;
279 Ok(())
280 }
281 fn fetch(&self, descriptor: &ModuleDescriptor, update_mode: UpdateMode) -> Result<(), String> {
282 let loader = self.get_loader_with_explicit_refresh(descriptor, update_mode)?;
283 let _ = loader.prepare(None, update_mode)?;
284 Ok(())
285 }
286 fn remove(&self, descriptor: &ModuleDescriptor) -> Result<(), String> {
287 let cache_dir_name = dir_name_from_descriptor(descriptor);
288 let mod_cache_dir = self.storage_dir.join(cache_dir_name);
289 if mod_cache_dir.is_dir() {
290 std::fs::remove_dir_all(mod_cache_dir).map_err(|e| e.to_string())?;
291 let mut local_toc = self.local_toc.lock().unwrap();
292 local_toc.remove_descriptor(descriptor)
293 } else {
294 Err("No such module in catalog".to_string())
295 }
296 }
297 fn fetch_newest_for_all(&self, update_mode: UpdateMode) -> Result<(), String> {
298 self.sync_toc(update_mode)?;
299 let iter = self.list_name_uid_pairs()
300 .ok_or_else(|| "managed catalog must support `list` method".to_string())?;
301 for (name, uid) in iter {
302
303 let upstream_bests: Vec<ModuleDescriptor> = self.upstream_catalogs.iter().filter_map(|upstream| {
305 upstream.lookup_newest_with_uid_and_version_req(&name, uid, None)
306 }).collect();
307 if let Some(newest_desc) = find_newest_module(upstream_bests.into_iter()) {
308 self.fetch(&newest_desc, update_mode)?;
309 }
310 }
311 Ok(())
312 }
313}
314
315#[derive(Debug)]
317struct LocalCatalogTOC {
318 mods_by_name: BTreeMap<String, Vec<ModuleDescriptor>>
319}
320
321impl LocalCatalogTOC {
322 fn build_from_dir(storage_dir: &Path) -> Result<Self, String> {
324 if !storage_dir.exists() {
325 std::fs::create_dir_all(&storage_dir).map_err(|e| e.to_string())?;
326 } else {
327 if !storage_dir.is_dir() {
328 return Err(format!("Found file instead of directory at {}", storage_dir.display()));
329 }
330 }
331
332 let mut new_self = Self {
333 mods_by_name: BTreeMap::new()
334 };
335
336 for dir_item_handle in std::fs::read_dir(storage_dir).map_err(|e| e.to_string())? {
337 let dir_entry = dir_item_handle.map_err(|e| e.to_string())?;
338 let file_name = dir_entry.file_name();
339 let name_str = file_name.to_str()
340 .ok_or_else(|| format!("Invalid characters found in local cache at path: {}", dir_entry.path().display()))?;
341
342 if !Self::should_ignore_dir_entry(name_str) {
343 let descriptor = parse_descriptor_from_dir_name(name_str)?;
344 new_self.add_descriptor(descriptor)?;
345 }
346 }
347
348 Ok(new_self)
349 }
350 fn should_ignore_dir_entry(dir_name: &str) -> bool {
352 if dir_name == "_catalog.repo" || dir_name == "_catalog.json" {
354 return true;
355 }
356 if dir_name.starts_with('.') {
358 return true;
359 }
360 false
361 }
362 fn lookup_by_name(&self, name: &str) -> Option<Vec<ModuleDescriptor>> {
363 if let Some(descriptors) = self.mods_by_name.get(name) {
364 if descriptors.len() > 0 {
365 return Some(descriptors.clone());
366 }
367 }
368 None
369 }
370 fn all_sorted_descriptors(&self) -> Vec<ModuleDescriptor> {
372 self.mods_by_name.iter().flat_map(|(_name, desc_vec)| desc_vec).cloned().collect()
373 }
374 fn add_descriptor(&mut self, descriptor: ModuleDescriptor) -> Result<(), String> {
376 let desc_vec = self.mods_by_name.entry(descriptor.name().to_owned()).or_insert(vec![]);
377 if !desc_vec.contains(&descriptor) {
378 desc_vec.push(descriptor);
379 desc_vec.sort_by(|a, b| a.version().cmp(&b.version()));
380 }
381 Ok(())
382 }
383 fn remove_descriptor(&mut self, descriptor: &ModuleDescriptor) -> Result<(), String> {
384 fn ret_err() -> Result<(), String> { Err("No such module in catalog".to_string()) }
385 match self.mods_by_name.get_mut(descriptor.name()) {
386 Some(desc_vec) => {
387 match desc_vec.iter().position(|vec_desc| vec_desc==descriptor) {
388 Some(idx) => {
389 desc_vec.remove(idx);
390 Ok(())
391 },
392 None => ret_err()
393 }
394 },
395 None => ret_err()
396 }
397 }
398}
399
400pub(crate) fn dir_name_from_descriptor(desc: &ModuleDescriptor) -> String {
403 let mod_dir_name = match desc.version() {
404 Some(version) => format!("{}@{version}", desc.name()),
405 None => desc.name().to_string()
406 };
407 match desc.uid() {
408 Some(uid) => format!("{mod_dir_name}#{uid:016x}"),
409 None => format!("{mod_dir_name}")
410 }
411}
412
413pub(crate) fn parse_descriptor_from_dir_name(dir_name: &str) -> Result<ModuleDescriptor, String> {
415 let (name_and_vers, uid) = match dir_name.rfind('#') {
416 Some(pos) => (&dir_name[0..pos], Some(&dir_name[pos+1..])),
417 None => (dir_name, None)
418 };
419 let (name, version) = match name_and_vers.find('@') {
420 Some(pos) => (&name_and_vers[0..pos], Some(&name_and_vers[pos+1..])),
421 None => (name_and_vers, None)
422 };
423 let version = match version {
424 Some(ver_str) => Some(semver::Version::parse(ver_str).map_err(|e| e.to_string())?),
425 None => None
426 };
427 let uid = match uid {
428 Some(uid_str) => Some(u64::from_str_radix(uid_str, 16).map_err(|e| e.to_string())?),
429 None => None
430 };
431 Ok(ModuleDescriptor::new(name.to_string(), version, uid))
432}