proto_core/layout/
inventory.rs

1use super::layout_error::ProtoLayoutError;
2use super::product::Product;
3use crate::helpers::{is_cache_enabled, is_offline};
4use crate::lockfile::LockRecord;
5use crate::tool_manifest::ToolManifest;
6use proto_pdk_api::{LoadVersionsOutput, ToolInventoryOptions};
7use starbase_utils::{fs, json, path};
8use std::path::PathBuf;
9use std::time::{Duration, SystemTime};
10use tracing::instrument;
11use version_spec::VersionSpec;
12
13#[derive(Clone, Debug, Default)]
14pub struct Inventory {
15    pub config: ToolInventoryOptions,
16    pub dir: PathBuf,
17    pub dir_original: Option<PathBuf>,
18    pub manifest: ToolManifest,
19    pub temp_dir: PathBuf,
20}
21
22impl Inventory {
23    #[instrument(skip(self))]
24    pub fn create_product(&self, spec: &VersionSpec) -> Product {
25        let mut name = spec.to_string();
26
27        if let Some(suffix) = &self.config.version_suffix {
28            name = format!("{name}{suffix}");
29        }
30
31        Product {
32            dir: self.dir.join(path::encode_component(name)),
33            version: spec.to_owned(),
34        }
35    }
36
37    pub fn get_locked_record(&self, version: &VersionSpec) -> Option<&LockRecord> {
38        self.manifest
39            .versions
40            .get(version)
41            .and_then(|man| man.lock.as_ref())
42    }
43
44    #[instrument(skip(self))]
45    pub fn load_remote_versions(
46        &self,
47        disable_cache: bool,
48    ) -> Result<Option<LoadVersionsOutput>, ProtoLayoutError> {
49        let cache_path = self
50            .dir_original
51            .as_ref()
52            .unwrap_or(&self.dir)
53            .join("remote-versions.json");
54
55        // Attempt to read from the cache first
56        if cache_path.exists() {
57            let mut read_cache =
58                // Check if cache is enabled here, so that we can handle offline below
59                if disable_cache || !is_cache_enabled() {
60                    false
61                // Otherwise, only read the cache every 12 hours
62                } else {
63                    let metadata = fs::metadata(&cache_path)?;
64
65                    if let Ok(modified_time) = metadata.modified().or_else(|_| metadata.created()) {
66                        modified_time > SystemTime::now() - Duration::from_secs(60 * 60 * 12)
67                    } else {
68                        false
69                    }
70                };
71
72            // If offline, always read the cache
73            if !read_cache && is_offline() {
74                read_cache = true;
75            }
76
77            if read_cache {
78                return Ok(Some(json::read_file(&cache_path)?));
79            }
80        }
81
82        Ok(None)
83    }
84
85    #[instrument(skip_all)]
86    pub fn save_remote_versions(&self, data: &LoadVersionsOutput) -> Result<(), ProtoLayoutError> {
87        json::write_file(
88            self.dir_original
89                .as_ref()
90                .unwrap_or(&self.dir)
91                .join("remote-versions.json"),
92            data,
93            false,
94        )?;
95
96        Ok(())
97    }
98}