vx_core/
installer.rs

1//! Installer utilities and types
2//!
3//! This module provides common installation functionality that can be used
4//! by tool implementations.
5
6use crate::Result;
7use serde::{Deserialize, Serialize};
8use std::path::{Path, PathBuf};
9
10/// Trait for installing tools
11#[async_trait::async_trait]
12pub trait Installer: Send + Sync {
13    /// Get the name of the tool this installer supports
14    fn tool_name(&self) -> &str;
15
16    /// Install a specific version of the tool
17    async fn install(&self, config: &InstallConfig) -> Result<PathBuf>;
18
19    /// Uninstall a specific version of the tool
20    async fn uninstall(&self, tool_name: &str, version: &str) -> Result<()>;
21
22    /// Check if a specific version is installed
23    async fn is_version_installed(&self, tool_name: &str, version: &str) -> Result<bool>;
24
25    /// Get the installation directory for a tool version
26    fn get_install_dir(&self, tool_name: &str, version: &str) -> PathBuf;
27
28    /// Get all installed versions of the tool
29    async fn get_installed_versions(&self, tool_name: &str) -> Result<Vec<String>>;
30
31    /// Verify installation integrity
32    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/// Configuration for tool installation
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct InstallConfig {
40    /// Name of the tool to install
41    pub tool_name: String,
42
43    /// Version to install
44    pub version: String,
45
46    /// Installation method
47    pub install_method: InstallMethod,
48
49    /// Download URL (if applicable)
50    pub download_url: Option<String>,
51
52    /// Installation directory
53    pub install_dir: PathBuf,
54
55    /// Whether to force reinstallation
56    pub force: bool,
57
58    /// Checksum for verification
59    pub checksum: Option<String>,
60
61    /// Additional configuration
62    pub metadata: std::collections::HashMap<String, String>,
63}
64
65/// Different methods for installing tools
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub enum InstallMethod {
68    /// Download and extract archive
69    Archive { format: ArchiveFormat },
70
71    /// Use system package manager
72    PackageManager { manager: String, package: String },
73
74    /// Run installation script
75    Script { url: String },
76
77    /// Download single binary
78    Binary,
79
80    /// Custom installation method
81    Custom { method: String },
82}
83
84/// Supported archive formats
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub enum ArchiveFormat {
87    Zip,
88    TarGz,
89    TarXz,
90    TarBz2,
91    SevenZip,
92}
93
94/// Installation progress callback
95pub type ProgressCallback = Box<dyn Fn(InstallProgress) + Send + Sync>;
96
97/// Installation progress information
98#[derive(Debug, Clone)]
99pub struct InstallProgress {
100    pub stage: InstallStage,
101    pub progress: f64, // 0.0 to 1.0
102    pub message: String,
103}
104
105/// Installation stages
106#[derive(Debug, Clone, PartialEq)]
107pub enum InstallStage {
108    Downloading,
109    Extracting,
110    Installing,
111    Verifying,
112    Completed,
113    Failed,
114}
115
116/// Installation result
117#[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    /// Create a new install config with minimal information
127    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    /// Set installation method
141    pub fn with_method(mut self, method: InstallMethod) -> Self {
142        self.install_method = method;
143        self
144    }
145
146    /// Set download URL
147    pub fn with_download_url(mut self, url: String) -> Self {
148        self.download_url = Some(url);
149        self
150    }
151
152    /// Set force reinstallation
153    pub fn with_force(mut self, force: bool) -> Self {
154        self.force = force;
155        self
156    }
157
158    /// Add metadata
159    pub fn with_metadata(mut self, key: String, value: String) -> Self {
160        self.metadata.insert(key, value);
161        self
162    }
163}
164
165/// Default installer implementation that provides common download and extraction functionality
166pub 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    /// Download and extract a file to the target directory
178    pub async fn download_and_extract(&self, url: &str, target_dir: &Path) -> Result<()> {
179        // Create target directory
180        std::fs::create_dir_all(target_dir)?;
181
182        // Download the file
183        let response = self.client.get(url).send().await?;
184        let bytes = response.bytes().await?;
185
186        // Determine file type and extract
187        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            // Assume it's a single binary
195            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    /// Extract ZIP archive
204    fn extract_zip(&self, data: &[u8], target_dir: &Path) -> Result<()> {
205        // This would use the zip crate to extract
206        // For now, just create a placeholder
207        let _ = (data, target_dir);
208        Ok(())
209    }
210
211    /// Extract tar.gz archive
212    fn extract_tar_gz(&self, data: &[u8], target_dir: &Path) -> Result<()> {
213        // This would use the tar and flate2 crates
214        // For now, just create a placeholder
215        let _ = (data, target_dir);
216        Ok(())
217    }
218
219    /// Extract tar.xz archive
220    fn extract_tar_xz(&self, data: &[u8], target_dir: &Path) -> Result<()> {
221        // This would use the tar and xz2 crates
222        // For now, just create a placeholder
223        let _ = (data, target_dir);
224        Ok(())
225    }
226}
227
228impl Default for DefaultInstaller {
229    fn default() -> Self {
230        Self::new()
231    }
232}