1#![allow(dead_code)]
3
4use std::collections::HashMap;
5use std::path::{Path, PathBuf};
6use std::sync::Mutex;
7use std::time::SystemTime;
8
9use serde::{Deserialize, Serialize};
10use tokio::fs;
11
12use super::loader::{get_plugin_cache_path, get_versioned_cache_path};
13use super::plugin_directories::get_plugins_directory;
14use super::plugin_identifier::{parse_plugin_identifier, setting_source_to_scope};
15use super::schemas::{PluginInstallationEntry, PluginScope};
16
17#[derive(Serialize, Deserialize, Debug, Clone, Default)]
19pub struct InstalledPluginsFileV2 {
20 pub version: u32,
21 pub plugins: HashMap<String, Vec<PluginInstallationEntry>>,
22}
23
24static MIGRATION_COMPLETED: Mutex<bool> = Mutex::new(false);
25static INSTALLED_PLUGINS_CACHE_V2: Mutex<Option<InstalledPluginsFileV2>> = Mutex::new(None);
26static IN_MEMORY_INSTALLED_PLUGINS: Mutex<Option<InstalledPluginsFileV2>> = Mutex::new(None);
27
28pub fn get_installed_plugins_file_path() -> PathBuf {
30 PathBuf::from(get_plugins_directory()).join("installed_plugins.json")
31}
32
33pub fn clear_installed_plugins_cache() {
35 let mut cache = INSTALLED_PLUGINS_CACHE_V2.lock().unwrap();
36 *cache = None;
37 let mut in_memory = IN_MEMORY_INSTALLED_PLUGINS.lock().unwrap();
38 *in_memory = None;
39 log::debug!("Cleared installed plugins cache");
40}
41
42fn read_installed_plugins_file_raw()
44-> Result<Option<(u64, serde_json::Value)>, Box<dyn std::error::Error + Send + Sync>> {
45 let file_path = get_installed_plugins_file_path();
46
47 let content = match std::fs::read_to_string(&file_path) {
48 Ok(c) => c,
49 Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
50 Err(e) => return Err(Box::new(e)),
51 };
52
53 let data: serde_json::Value = serde_json::from_str(&content)?;
54 let version = data.get("version").and_then(|v| v.as_u64()).unwrap_or(1);
55 Ok(Some((version, data)))
56}
57
58pub fn load_installed_plugins_v2()
60-> Result<InstalledPluginsFileV2, Box<dyn std::error::Error + Send + Sync>> {
61 {
62 let cache = INSTALLED_PLUGINS_CACHE_V2.lock().unwrap();
63 if let Some(ref data) = *cache {
64 return Ok(data.clone());
65 }
66 }
67
68 let file_path = get_installed_plugins_file_path();
69
70 let result = match read_installed_plugins_file_raw() {
71 Ok(Some((2, data))) => {
72 let validated: InstalledPluginsFileV2 = serde_json::from_value(data)?;
73 validated
74 }
75 Ok(Some((1, data))) => migrate_v1_to_v2(&data)?,
76 Ok(Some((_version, _data))) => {
77 log::debug!(
78 "installed_plugins.json has unsupported version, returning empty V2 object"
79 );
80 InstalledPluginsFileV2 {
81 version: 2,
82 plugins: HashMap::new(),
83 }
84 }
85 Ok(None) => {
86 log::debug!("installed_plugins.json doesn't exist, returning empty V2 object");
87 InstalledPluginsFileV2 {
88 version: 2,
89 plugins: HashMap::new(),
90 }
91 }
92 Err(e) => {
93 log::debug!("Failed to read installed_plugins.json: {}", e);
94 InstalledPluginsFileV2 {
95 version: 2,
96 plugins: HashMap::new(),
97 }
98 }
99 };
100
101 {
102 let mut cache = INSTALLED_PLUGINS_CACHE_V2.lock().unwrap();
103 *cache = Some(result.clone());
104 }
105
106 Ok(result)
107}
108
109fn migrate_v1_to_v2(
111 _v1_data: &serde_json::Value,
112) -> Result<InstalledPluginsFileV2, Box<dyn std::error::Error + Send + Sync>> {
113 Ok(InstalledPluginsFileV2 {
114 version: 2,
115 plugins: HashMap::new(),
116 })
117}
118
119fn save_installed_plugins_v2(
121 data: &InstalledPluginsFileV2,
122) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
123 let file_path = get_installed_plugins_file_path();
124
125 std::fs::create_dir_all(get_plugins_directory())?;
126
127 let json_content = serde_json::to_string_pretty(data)?;
128 std::fs::write(&file_path, json_content)?;
129
130 {
131 let mut cache = INSTALLED_PLUGINS_CACHE_V2.lock().unwrap();
132 *cache = Some(data.clone());
133 }
134
135 log::debug!(
136 "Saved {} installed plugins to {:?}",
137 data.plugins.len(),
138 file_path
139 );
140 Ok(())
141}
142
143pub fn add_plugin_installation(
145 plugin_id: &str,
146 scope: PluginScope,
147 install_path: &str,
148 metadata: &PluginInstallationEntry,
149 project_path: Option<&str>,
150) {
151 let mut data = match load_installed_plugins_from_disk() {
152 Ok(d) => d,
153 Err(e) => {
154 log::error!("Failed to load installed plugins: {}", e);
155 return;
156 }
157 };
158
159 let installations = data.plugins.entry(plugin_id.to_string()).or_default();
160
161 let existing_index = installations
162 .iter()
163 .position(|entry| entry.scope == scope && entry.project_path.as_deref() == project_path);
164
165 let now = SystemTime::now()
166 .duration_since(SystemTime::UNIX_EPOCH)
167 .unwrap_or_default()
168 .as_millis();
169
170 let new_entry = PluginInstallationEntry {
171 scope: scope.clone(),
172 install_path: install_path.to_string(),
173 version: metadata.version.clone(),
174 installed_at: metadata.installed_at.clone(),
175 last_updated: now.to_string(),
176 git_commit_sha: metadata.git_commit_sha.clone(),
177 project_path: project_path.map(|s| s.to_string()),
178 };
179
180 if let Some(idx) = existing_index {
181 installations[idx] = new_entry;
182 log::debug!(
183 "Updated installation for {} at scope {:?}",
184 plugin_id,
185 scope
186 );
187 } else {
188 installations.push(new_entry);
189 log::debug!("Added installation for {} at scope {:?}", plugin_id, scope);
190 }
191
192 if let Err(e) = save_installed_plugins_v2(&data) {
193 log::error!("Failed to save installed plugins: {}", e);
194 }
195}
196
197pub fn remove_plugin_installation(plugin_id: &str, scope: PluginScope, project_path: Option<&str>) {
199 let mut data = match load_installed_plugins_from_disk() {
200 Ok(d) => d,
201 Err(_) => return,
202 };
203
204 if let Some(installations) = data.plugins.get_mut(plugin_id) {
205 installations.retain(|entry| {
206 !(entry.scope == scope && entry.project_path.as_deref() == project_path)
207 });
208
209 if installations.is_empty() {
210 data.plugins.remove(plugin_id);
211 }
212 }
213
214 let _ = save_installed_plugins_v2(&data);
215 log::debug!(
216 "Removed installation for {} at scope {:?}",
217 plugin_id,
218 scope
219 );
220}
221
222pub fn load_installed_plugins_from_disk()
224-> Result<InstalledPluginsFileV2, Box<dyn std::error::Error + Send + Sync>> {
225 let file_path = get_installed_plugins_file_path();
226
227 let content = match std::fs::read_to_string(&file_path) {
228 Ok(c) => c,
229 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
230 return Ok(InstalledPluginsFileV2 {
231 version: 2,
232 plugins: HashMap::new(),
233 });
234 }
235 Err(e) => return Err(e.into()),
236 };
237
238 let data: serde_json::Value = serde_json::from_str(&content)?;
239 let version = data.get("version").and_then(|v| v.as_u64()).unwrap_or(1);
240
241 if version == 2 {
242 let validated: InstalledPluginsFileV2 = serde_json::from_value(data)?;
243 Ok(validated)
244 } else {
245 migrate_v1_to_v2(&data)
246 }
247}
248
249pub fn is_plugin_installed(plugin_id: &str) -> bool {
251 match load_installed_plugins_v2() {
252 Ok(data) => data.plugins.contains_key(plugin_id),
253 Err(_) => false,
254 }
255}
256
257pub fn remove_all_plugins_for_marketplace(marketplace_name: &str) -> (Vec<String>, Vec<String>) {
259 if marketplace_name.is_empty() {
260 return (Vec::new(), Vec::new());
261 }
262
263 let mut data = match load_installed_plugins_from_disk() {
264 Ok(d) => d,
265 Err(_) => return (Vec::new(), Vec::new()),
266 };
267
268 let suffix = format!("@{}", marketplace_name);
269 let mut orphaned_paths = Vec::new();
270 let mut removed_plugin_ids = Vec::new();
271
272 let plugin_ids: Vec<String> = data.plugins.keys().cloned().collect();
273 for plugin_id in plugin_ids {
274 if !plugin_id.ends_with(&suffix) {
275 continue;
276 }
277
278 if let Some(entries) = data.plugins.remove(&plugin_id) {
279 for entry in entries {
280 orphaned_paths.push(entry.install_path);
281 }
282 }
283 removed_plugin_ids.push(plugin_id.clone());
284 log::debug!(
285 "Removed installed plugin for marketplace removal: {}",
286 plugin_id
287 );
288 }
289
290 if !removed_plugin_ids.is_empty() {
291 let _ = save_installed_plugins_v2(&data);
292 }
293
294 (orphaned_paths, removed_plugin_ids)
295}
296
297pub fn get_in_memory_installed_plugins() -> InstalledPluginsFileV2 {
299 let mut in_memory = IN_MEMORY_INSTALLED_PLUGINS.lock().unwrap();
300 if in_memory.is_none() {
301 *in_memory = load_installed_plugins_v2().ok();
302 }
303 in_memory.clone().unwrap_or_default()
304}
305
306pub async fn initialize_versioned_plugins() -> Result<(), Box<dyn std::error::Error + Send + Sync>>
308{
309 migrate_to_single_plugin_file();
310
311 if let Err(e) = migrate_from_enabled_plugins().await {
312 log::error!("Failed to migrate from enabled plugins: {}", e);
313 }
314
315 let data = get_in_memory_installed_plugins();
316 log::debug!(
317 "Initialized versioned plugins system with {} plugins",
318 data.plugins.len()
319 );
320 Ok(())
321}
322
323fn migrate_to_single_plugin_file() {
324 let mut completed = MIGRATION_COMPLETED.lock().unwrap();
325 if *completed {
326 return;
327 }
328 *completed = true;
329}
330
331pub async fn migrate_from_enabled_plugins() -> Result<(), Box<dyn std::error::Error + Send + Sync>>
333{
334 Ok(())
335}
336
337pub struct PendingUpdateDetails {
339 pub plugin_id: String,
340 pub version: String,
341}
342
343pub fn has_pending_updates() -> bool {
345 false
346}
347
348pub fn get_pending_updates_details() -> Vec<PendingUpdateDetails> {
350 Vec::new()
351}
352
353pub fn is_installation_relevant_to_current_project(_entry: &PluginInstallationEntry) -> bool {
355 true
356}