vtcode_core/marketplace/
config.rs1use std::path::{Path, PathBuf};
6
7use anyhow::{Context, Result, bail};
8use serde::{Deserialize, Serialize};
9
10use crate::marketplace::MarketplaceSource;
11use crate::utils::file_utils::{read_file_with_context, write_file_with_context};
12
13#[derive(Debug, Clone, Deserialize, Serialize, Default)]
15pub struct MarketplaceSettings {
16 #[serde(default)]
18 pub marketplaces: Vec<MarketplaceSource>,
19
20 #[serde(default)]
22 pub installed_plugins: Vec<InstalledPlugin>,
23
24 #[serde(default)]
26 pub auto_update: AutoUpdateSettings,
27
28 #[serde(default)]
30 pub security: SecuritySettings,
31}
32
33#[derive(Debug, Clone, Deserialize, Serialize)]
35pub struct InstalledPlugin {
36 pub id: String,
38
39 pub name: String,
41
42 pub version: String,
44
45 pub source: String,
47
48 pub install_path: PathBuf,
50
51 pub enabled: bool,
53
54 pub trust_level: crate::config::PluginTrustLevel,
56
57 pub installed_at: String, }
60
61#[derive(Debug, Clone, Deserialize, Serialize)]
63pub struct AutoUpdateSettings {
64 #[serde(default = "default_true")]
66 pub marketplaces: bool,
67
68 #[serde(default = "default_true")]
70 pub plugins: bool,
71
72 #[serde(default = "default_update_interval")]
74 pub check_interval_hours: u32,
75}
76
77#[derive(Debug, Clone, Deserialize, Serialize)]
79pub struct SecuritySettings {
80 #[serde(default)]
82 pub default_trust_level: crate::config::PluginTrustLevel,
83
84 #[serde(default = "default_true")]
86 pub require_confirmation: bool,
87
88 #[serde(default)]
90 pub allowed_sources: Vec<String>,
91
92 #[serde(default)]
94 pub blocked_sources: Vec<String>,
95}
96
97impl Default for AutoUpdateSettings {
98 fn default() -> Self {
99 Self {
100 marketplaces: true,
101 plugins: true,
102 check_interval_hours: 24,
103 }
104 }
105}
106
107impl Default for SecuritySettings {
108 fn default() -> Self {
109 Self {
110 default_trust_level: crate::config::PluginTrustLevel::Sandbox,
111 require_confirmation: true,
112 allowed_sources: Vec::new(),
113 blocked_sources: Vec::new(),
114 }
115 }
116}
117
118fn default_true() -> bool {
119 true
120}
121
122fn default_update_interval() -> u32 {
123 24
124}
125
126impl MarketplaceSettings {
127 pub async fn load_from_file(config_path: &Path) -> Result<Self> {
129 if !tokio::fs::try_exists(config_path).await.unwrap_or(false) {
130 return Ok(Self::default());
132 }
133
134 let content = read_file_with_context(config_path, "marketplace config file").await?;
135
136 let settings: MarketplaceSettings = toml::from_str(&content)
137 .with_context(|| format!("Failed to parse marketplace config file: {}", config_path.display()))?;
138
139 Ok(settings)
140 }
141
142 pub async fn save_to_file(&self, config_path: &Path) -> Result<()> {
144 let content = toml::to_string(&self).with_context(|| "Failed to serialize marketplace settings")?;
145
146 write_file_with_context(config_path, &content, "marketplace config file").await?;
147
148 Ok(())
149 }
150
151 pub fn add_marketplace(&mut self, marketplace: MarketplaceSource) {
153 if !self.marketplaces.iter().any(|m| m.id() == marketplace.id()) {
155 self.marketplaces.push(marketplace);
156 }
157 }
158
159 pub fn remove_marketplace(&mut self, id: &str) -> bool {
161 let initial_len = self.marketplaces.len();
162 self.marketplaces.retain(|m| m.id() != id);
163 self.marketplaces.len() != initial_len
164 }
165
166 pub fn add_installed_plugin(&mut self, plugin: InstalledPlugin) {
168 match self.installed_plugins.iter_mut().find(|p| p.id == plugin.id) {
170 Some(existing) => {
171 existing.name = plugin.name;
173 existing.version = plugin.version;
174 existing.source = plugin.source;
175 existing.install_path = plugin.install_path;
176 existing.enabled = plugin.enabled;
177 existing.trust_level = plugin.trust_level;
178 existing.installed_at = plugin.installed_at;
179 }
180 None => {
181 self.installed_plugins.push(plugin);
182 }
183 }
184 }
185
186 pub fn get_installed_plugin(&self, plugin_id: &str) -> Option<&InstalledPlugin> {
188 self.installed_plugins.iter().find(|p| p.id == plugin_id)
189 }
190
191 pub fn enable_plugin(&mut self, plugin_id: &str) -> Result<()> {
193 match self.installed_plugins.iter_mut().find(|p| p.id == plugin_id) {
194 Some(plugin) => {
195 plugin.enabled = true;
196 Ok(())
197 }
198 None => bail!("Plugin '{plugin_id}' not found in installed plugins"),
199 }
200 }
201
202 pub fn disable_plugin(&mut self, plugin_id: &str) -> Result<()> {
204 match self.installed_plugins.iter_mut().find(|p| p.id == plugin_id) {
205 Some(plugin) => {
206 plugin.enabled = false;
207 Ok(())
208 }
209 None => bail!("Plugin '{plugin_id}' not found in installed plugins"),
210 }
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 #[tokio::test]
219 async fn test_marketplace_settings_default() {
220 let settings = MarketplaceSettings::default();
221 assert!(settings.marketplaces.is_empty());
222 assert!(settings.installed_plugins.is_empty());
223 assert!(settings.auto_update.marketplaces);
224 assert_eq!(settings.auto_update.check_interval_hours, 24);
225 assert_eq!(settings.security.default_trust_level, crate::config::PluginTrustLevel::Sandbox);
226 }
227
228 #[tokio::test]
229 async fn test_marketplace_settings_save_load() {
230 let temp_dir = tempfile::tempdir().unwrap();
231 let config_path = temp_dir.path().join("test_marketplace.toml");
232
233 let mut settings = MarketplaceSettings::default();
234 settings.auto_update.marketplaces = false;
235 settings.security.default_trust_level = crate::config::PluginTrustLevel::Trusted;
236
237 settings.save_to_file(&config_path).await.unwrap();
238
239 let loaded_settings = MarketplaceSettings::load_from_file(&config_path).await.unwrap();
240 assert_eq!(settings.auto_update.marketplaces, loaded_settings.auto_update.marketplaces);
241 assert_eq!(settings.security.default_trust_level, loaded_settings.security.default_trust_level);
242 }
243
244 #[tokio::test]
245 async fn test_add_remove_marketplace() {
246 let mut settings = MarketplaceSettings::default();
247
248 let marketplace = MarketplaceSource::Git {
249 id: "test".to_string(),
250 url: "https://example.com/test".to_string(),
251 refspec: None,
252 };
253
254 settings.add_marketplace(marketplace.clone());
255 assert_eq!(settings.marketplaces.len(), 1);
256
257 settings.remove_marketplace("test");
258 assert_eq!(settings.marketplaces.len(), 0);
259 }
260
261 #[tokio::test]
262 async fn test_plugin_enable_disable() {
263 let mut settings = MarketplaceSettings::default();
264
265 let plugin = InstalledPlugin {
266 id: "test-plugin".to_string(),
267 name: "Test Plugin".to_string(),
268 version: "1.0.0".to_string(),
269 source: "test-marketplace".to_string(),
270 install_path: PathBuf::from("/tmp/test"),
271 enabled: false,
272 trust_level: crate::config::PluginTrustLevel::Sandbox,
273 installed_at: "2023-01-01".to_string(),
274 };
275
276 settings.add_installed_plugin(plugin);
277
278 settings.enable_plugin("test-plugin").unwrap();
279 assert!(settings.get_installed_plugin("test-plugin").unwrap().enabled);
280
281 settings.disable_plugin("test-plugin").unwrap();
282 assert!(!settings.get_installed_plugin("test-plugin").unwrap().enabled);
283 }
284}