Skip to main content

vs_core/service/
update.rs

1use vs_plugin_api::PluginBackendKind;
2use vs_registry::RegistryEntry;
3
4use crate::plugin_source::is_remote_source;
5use crate::registry_source::{
6    fetch_plugin_manifest, fetch_plugin_manifest_from_url, fetch_url_text,
7    is_remote_registry_source, parse_registry_entries, registry_index_url,
8};
9use crate::{App, CoreError};
10
11impl App {
12    /// Refreshes the searchable plugin index from the configured registry source.
13    pub fn update_registry(&self) -> Result<usize, CoreError> {
14        let config = self.app_config()?;
15        let source = config.registry.address;
16        if source.is_empty() {
17            return Err(CoreError::Unsupported(String::from(
18                "registry.address is not configured",
19            )));
20        }
21        let registry_source = if is_remote_registry_source(&source) {
22            registry_index_url(&source)
23        } else {
24            source
25        };
26        let mut entries = if is_remote_registry_source(&registry_source) {
27            let content = fetch_url_text(&registry_source)?;
28            parse_registry_entries(&content).map_err(|error| CoreError::RegistrySource {
29                path: registry_source.clone().into(),
30                message: error.to_string(),
31            })?
32        } else {
33            let path = self.normalize_source_path(&registry_source);
34            let content =
35                std::fs::read_to_string(&path).map_err(|error| CoreError::RegistrySource {
36                    path: path.clone(),
37                    message: error.to_string(),
38                })?;
39            parse_registry_entries(&content).map_err(|error| CoreError::RegistrySource {
40                path: path.clone(),
41                message: error.to_string(),
42            })?
43        };
44
45        if !is_remote_registry_source(&registry_source) {
46            let path = self.normalize_source_path(&registry_source);
47            let base_dir = path.parent().unwrap_or(&self.cwd);
48            for entry in &mut entries {
49                let source_path = std::path::PathBuf::from(&entry.source);
50                if source_path.is_relative() {
51                    entry.source = base_dir.join(source_path).display().to_string();
52                }
53            }
54        }
55
56        entries.retain(|entry| self.ensure_backend_supported(entry.backend).is_ok());
57        self.registry.replace_available_plugins(&entries)?;
58        Ok(entries.len())
59    }
60
61    /// Updates a locally added plugin from its manifest URL or registry metadata.
62    pub fn update_plugin(&self, name: &str) -> Result<RegistryEntry, CoreError> {
63        let entry = self
64            .added_plugins()?
65            .into_iter()
66            .find(|entry| entry.matches(name))
67            .ok_or_else(|| CoreError::UnknownPlugin(name.to_string()))?;
68        let materialized = self.materialize_plugin_entry(&entry)?;
69        let plugin = self.load_plugin(&materialized)?;
70        let manifest = plugin.manifest().clone();
71
72        let mut refreshed = RegistryEntry {
73            name: entry.name.clone(),
74            source: entry.source.clone(),
75            backend: entry.backend,
76            description: manifest.description.clone().or(entry.description.clone()),
77            aliases: entry.aliases.clone(),
78        };
79
80        if let Some(source) =
81            self.resolve_plugin_update_source(&entry, &manifest.name, &manifest)?
82        {
83            refreshed.source = source;
84        }
85
86        let refreshed = if is_remote_source(&refreshed.source) {
87            self.materialize_plugin_entry_with_refresh(&refreshed, true)?
88        } else {
89            self.materialize_plugin_entry(&refreshed)?
90        };
91        self.registry.add_plugin(refreshed.clone())?;
92        Ok(refreshed)
93    }
94
95    /// Updates every locally added plugin.
96    pub fn update_all_plugins(&self) -> Result<Vec<RegistryEntry>, CoreError> {
97        let entries = self.added_plugins()?;
98        let mut updated = Vec::new();
99        for entry in entries {
100            updated.push(self.update_plugin(&entry.name)?);
101        }
102        Ok(updated)
103    }
104
105    fn resolve_plugin_update_source(
106        &self,
107        entry: &RegistryEntry,
108        manifest_name: &str,
109        manifest: &vs_plugin_api::PluginManifest,
110    ) -> Result<Option<String>, CoreError> {
111        if let Some(manifest_url) = manifest
112            .manifest_url
113            .as_deref()
114            .or(manifest.update_url.as_deref())
115        {
116            let plugin_manifest = fetch_plugin_manifest_from_url(manifest_url)?;
117            if !plugin_manifest.download_url.is_empty() {
118                return Ok(Some(plugin_manifest.download_url));
119            }
120        }
121
122        if entry.backend == PluginBackendKind::Lua {
123            let config = self.app_config()?;
124            if !config.registry.address.is_empty() {
125                if let Ok(plugin_manifest) =
126                    fetch_plugin_manifest(&config.registry.address, manifest_name)
127                {
128                    if !plugin_manifest.download_url.is_empty() {
129                        return Ok(Some(plugin_manifest.download_url));
130                    }
131                }
132            }
133        }
134
135        if is_remote_source(&entry.source) {
136            return Ok(Some(entry.source.clone()));
137        }
138
139        Ok(None)
140    }
141}