Skip to main content

vs_core/service/
update.rs

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