1use std::fs;
4
5use crate::{App, CoreError, InstalledVersion};
6
7impl App {
8 pub fn list_installed_versions(&self) -> Result<Vec<InstalledVersion>, CoreError> {
10 let cache_root = self.home().join("cache");
11 if !cache_root.exists() {
12 return Ok(Vec::new());
13 }
14 let mut installed = Vec::new();
15
16 for plugin_entry in fs::read_dir(cache_root)? {
17 let plugin_entry = plugin_entry?;
18 if !plugin_entry.file_type()?.is_dir() {
19 continue;
20 }
21 let plugin_name = match plugin_entry.file_name().into_string() {
22 Ok(name) => name,
23 Err(_) => continue,
24 };
25 let versions_root = plugin_entry.path().join("versions");
26 if !versions_root.exists() {
27 continue;
28 }
29 for version_entry in fs::read_dir(&versions_root)? {
30 let version_entry = version_entry?;
31 if !version_entry.file_type()?.is_dir() {
32 continue;
33 }
34 if let Ok(version) = version_entry.file_name().into_string() {
35 installed.push(InstalledVersion {
36 plugin: plugin_name.clone(),
37 version,
38 install_dir: version_entry.path(),
39 });
40 }
41 }
42 }
43
44 installed.sort_by(|left, right| {
45 left.plugin
46 .cmp(&right.plugin)
47 .then(compare_versions_desc(&left.version, &right.version))
48 });
49 Ok(installed)
50 }
51}
52
53fn compare_versions_desc(left: &str, right: &str) -> std::cmp::Ordering {
54 compare_version_components(left, right).reverse()
55}
56
57fn compare_version_components(left: &str, right: &str) -> std::cmp::Ordering {
58 let left_parts = version_components(left);
59 let right_parts = version_components(right);
60 for (left_part, right_part) in left_parts.iter().zip(right_parts.iter()) {
61 let ordering = match (left_part.parse::<u64>(), right_part.parse::<u64>()) {
62 (Ok(left_num), Ok(right_num)) => left_num.cmp(&right_num),
63 _ => left_part.cmp(right_part),
64 };
65 if ordering != std::cmp::Ordering::Equal {
66 return ordering;
67 }
68 }
69 left_parts.len().cmp(&right_parts.len())
70}
71
72fn version_components(version: &str) -> Vec<&str> {
73 version
74 .trim_start_matches('v')
75 .split(|ch: char| !ch.is_ascii_alphanumeric())
76 .filter(|part| !part.is_empty())
77 .collect()
78}