1use crate::Result;
7use serde::{Deserialize, Serialize};
8use std::path::{Path, PathBuf};
9
10#[async_trait::async_trait]
12pub trait Installer: Send + Sync {
13 fn tool_name(&self) -> &str;
15
16 async fn install(&self, config: &InstallConfig) -> Result<PathBuf>;
18
19 async fn uninstall(&self, tool_name: &str, version: &str) -> Result<()>;
21
22 async fn is_version_installed(&self, tool_name: &str, version: &str) -> Result<bool>;
24
25 fn get_install_dir(&self, tool_name: &str, version: &str) -> PathBuf;
27
28 async fn get_installed_versions(&self, tool_name: &str) -> Result<Vec<String>>;
30
31 async fn verify_installation(&self, tool_name: &str, version: &str) -> Result<bool> {
33 self.is_version_installed(tool_name, version).await
34 }
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct InstallConfig {
40 pub tool_name: String,
42
43 pub version: String,
45
46 pub install_method: InstallMethod,
48
49 pub download_url: Option<String>,
51
52 pub install_dir: PathBuf,
54
55 pub force: bool,
57
58 pub checksum: Option<String>,
60
61 pub metadata: std::collections::HashMap<String, String>,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67pub enum InstallMethod {
68 Archive { format: ArchiveFormat },
70
71 PackageManager { manager: String, package: String },
73
74 Script { url: String },
76
77 Binary,
79
80 Custom { method: String },
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86pub enum ArchiveFormat {
87 Zip,
88 TarGz,
89 TarXz,
90 TarBz2,
91 SevenZip,
92}
93
94pub type ProgressCallback = Box<dyn Fn(InstallProgress) + Send + Sync>;
96
97#[derive(Debug, Clone)]
99pub struct InstallProgress {
100 pub stage: InstallStage,
101 pub progress: f64, pub message: String,
103}
104
105#[derive(Debug, Clone, PartialEq)]
107pub enum InstallStage {
108 Downloading,
109 Extracting,
110 Installing,
111 Verifying,
112 Completed,
113 Failed,
114}
115
116#[derive(Debug)]
118pub struct InstallResult {
119 pub success: bool,
120 pub installed_path: Option<PathBuf>,
121 pub error_message: Option<String>,
122 pub installation_time: std::time::Duration,
123}
124
125impl InstallConfig {
126 pub fn new(tool_name: String, version: String, install_dir: PathBuf) -> Self {
128 Self {
129 tool_name,
130 version,
131 install_method: InstallMethod::Binary,
132 download_url: None,
133 install_dir,
134 force: false,
135 checksum: None,
136 metadata: std::collections::HashMap::new(),
137 }
138 }
139
140 pub fn with_method(mut self, method: InstallMethod) -> Self {
142 self.install_method = method;
143 self
144 }
145
146 pub fn with_download_url(mut self, url: String) -> Self {
148 self.download_url = Some(url);
149 self
150 }
151
152 pub fn with_force(mut self, force: bool) -> Self {
154 self.force = force;
155 self
156 }
157
158 pub fn with_metadata(mut self, key: String, value: String) -> Self {
160 self.metadata.insert(key, value);
161 self
162 }
163}
164
165pub struct DefaultInstaller {
167 client: reqwest::Client,
168}
169
170impl DefaultInstaller {
171 pub fn new() -> Self {
172 Self {
173 client: reqwest::Client::new(),
174 }
175 }
176
177 pub async fn download_and_extract(&self, url: &str, target_dir: &Path) -> Result<()> {
179 std::fs::create_dir_all(target_dir)?;
181
182 let response = self.client.get(url).send().await?;
184 let bytes = response.bytes().await?;
185
186 if url.ends_with(".zip") {
188 self.extract_zip(&bytes, target_dir)?;
189 } else if url.ends_with(".tar.gz") || url.ends_with(".tgz") {
190 self.extract_tar_gz(&bytes, target_dir)?;
191 } else if url.ends_with(".tar.xz") {
192 self.extract_tar_xz(&bytes, target_dir)?;
193 } else {
194 let filename = url.split('/').next_back().unwrap_or("binary");
196 let target_path = target_dir.join(filename);
197 std::fs::write(target_path, &bytes)?;
198 }
199
200 Ok(())
201 }
202
203 fn extract_zip(&self, data: &[u8], target_dir: &Path) -> Result<()> {
205 let _ = (data, target_dir);
208 Ok(())
209 }
210
211 fn extract_tar_gz(&self, data: &[u8], target_dir: &Path) -> Result<()> {
213 let _ = (data, target_dir);
216 Ok(())
217 }
218
219 fn extract_tar_xz(&self, data: &[u8], target_dir: &Path) -> Result<()> {
221 let _ = (data, target_dir);
224 Ok(())
225 }
226}
227
228impl Default for DefaultInstaller {
229 fn default() -> Self {
230 Self::new()
231 }
232}