tidepool_version_manager/
go.rs

1// Go version management module
2use crate::{
3    downloader::{Downloader, ProgressReporter},
4    InstallRequest, ListInstalledRequest, RuntimeStatus, StatusRequest, SwitchRequest,
5    UninstallRequest, VersionInfo, VersionList, VersionManager,
6};
7use log::{debug, info, warn};
8use sha2::{Digest, Sha256};
9use std::collections::HashMap;
10use std::path::{Path, PathBuf};
11use tokio::io::AsyncReadExt;
12
13/// Detailed information about a Go version
14#[derive(Debug, Clone)]
15pub struct GoVersionInfo {
16    /// Version number (e.g., "1.21.0")
17    pub version: String,
18    /// Operating system (e.g., "linux", "windows", "darwin")
19    pub os: String,
20    /// Architecture (e.g., "amd64", "arm64", "386")
21    pub arch: String,
22    /// File extension (e.g., "tar.gz", "zip")
23    pub extension: String,
24    /// Complete filename (e.g., "go1.21.0.linux-amd64.tar.gz")
25    pub filename: String,
26    /// Download URL
27    pub download_url: String,
28    /// Official SHA256 checksum
29    pub sha256: Option<String>,
30    /// File size in bytes
31    pub size: Option<u64>,
32    /// Whether it's installed
33    pub is_installed: bool,
34    /// Whether it's cached
35    pub is_cached: bool,
36    /// Local installation path (if installed)
37    pub install_path: Option<PathBuf>,
38    /// Cache file path (if cached)
39    pub cache_path: Option<PathBuf>,
40}
41
42pub struct GoManager {}
43
44impl Default for GoManager {
45    fn default() -> Self {
46        Self::new()
47    }
48}
49
50impl GoManager {
51    #[must_use]
52    pub fn new() -> Self {
53        Self {}
54    }
55    /// Extract archive to specified directory (public method)
56    ///
57    /// # Errors
58    ///
59    /// Returns an error if:
60    /// - The archive file cannot be opened
61    /// - The archive format is invalid
62    /// - Files cannot be extracted to the target directory
63    /// - File I/O operations fail
64    pub fn extract_archive(&self, archive_path: &Path, extract_to: &Path) -> Result<(), String> {
65        #[cfg(target_os = "windows")]
66        {
67            Self::extract_zip(archive_path, extract_to)
68        }
69
70        #[cfg(not(target_os = "windows"))]
71        {
72            self.extract_tar_gz(archive_path, extract_to)
73        }
74    }
75    /// 跨平台版本切换实现(Windows 使用 Junction,Unix 使用符号链接)
76    ///
77    /// # Errors
78    ///
79    /// Returns an error if:
80    /// - The requested version directory does not exist
81    /// - Unable to remove existing current version link
82    /// - Unable to create new version link
83    /// - System file operations fail
84    pub fn switch_version(&self, version: &str, base_dir: &Path) -> Result<(), String> {
85        #[cfg(target_os = "windows")]
86        {
87            Self::switch_version_windows(version, base_dir)
88        }
89
90        #[cfg(not(target_os = "windows"))]
91        {
92            self.switch_version_unix(version, base_dir)
93        }
94    }
95    /// Windows Junction Point 版本切换实现(不需要管理员权限)
96    #[cfg(target_os = "windows")]
97    fn switch_version_windows(version: &str, base_dir: &Path) -> Result<(), String> {
98        let version_path = base_dir.join(version);
99        let junction_path = base_dir.join("current");
100
101        if !version_path.exists() {
102            return Err(format!("Go version {version} is not installed"));
103        }
104
105        // 验证Go安装的完整性
106        let go_exe = version_path.join("bin").join("go.exe");
107        if !go_exe.exists() {
108            return Err(format!(
109                "Invalid Go installation: missing go.exe in {}",
110                version_path.display()
111            ));
112        }
113
114        debug!("Creating junction point for Go version {version}");
115
116        // 删除现有的junction或目录
117        if junction_path.exists() && junction_path.is_dir() {
118            std::fs::remove_dir_all(&junction_path)
119                .map_err(|e| format!("Failed to remove existing directory: {e}"))?;
120        }
121
122        // 使用mklink命令创建junction (不需要管理员权限)
123        let output = std::process::Command::new("cmd")
124            .args([
125                "/C",
126                "mklink",
127                "/J",
128                &junction_path.to_string_lossy(),
129                &version_path.to_string_lossy(),
130            ])
131            .output()
132            .map_err(|e| format!("Failed to execute mklink: {e}"))?;
133
134        if !output.status.success() {
135            let error_msg = String::from_utf8_lossy(&output.stderr);
136            return Err(format!("Failed to create junction: {error_msg}"));
137        }
138
139        info!("Successfully created junction point for Go version {version}");
140
141        // 验证junction是否正确创建
142        if !junction_path.exists() {
143            return Err("Junction does not exist after creation".to_string());
144        }
145
146        let junction_go_exe = junction_path.join("bin").join("go.exe");
147        if !junction_go_exe.exists() {
148            return Err("Junction target is invalid: missing go.exe".to_string());
149        }
150        debug!("Successfully created junction point for Go version {version}");
151        debug!("Environment variables updated for Go version {version}");
152        Ok(())
153    }
154
155    /// Unix Symlink 版本切换实现(使用符号链接)
156    #[cfg(not(target_os = "windows"))]
157    fn switch_version_unix(&self, version: &str, base_dir: &Path) -> Result<(), String> {
158        let version_path = base_dir.join(version);
159        let current_path = base_dir.join("current");
160
161        if !version_path.exists() {
162            return Err(format!("Go version {} is not installed", version));
163        }
164
165        // 验证Go安装的完整性
166        let go_binary = version_path.join("bin").join("go");
167        if !go_binary.exists() {
168            return Err(format!(
169                "Invalid Go installation: missing go binary in {}",
170                version_path.display()
171            ));
172        }
173
174        debug!("Creating symlink for Go version {}", version);
175
176        // 删除现有的符号链接或目录
177        if current_path.exists() {
178            if current_path.is_symlink() {
179                std::fs::remove_file(&current_path)
180                    .map_err(|e| format!("Failed to remove existing symlink: {}", e))?;
181            } else if current_path.is_dir() {
182                std::fs::remove_dir_all(&current_path)
183                    .map_err(|e| format!("Failed to remove existing directory: {}", e))?;
184            }
185        }
186
187        // 创建符号链接
188        #[cfg(not(target_os = "windows"))]
189        {
190            std::os::unix::fs::symlink(&version_path, &current_path)
191                .map_err(|e| format!("Failed to create symlink: {}", e))?;
192        }
193
194        info!("Successfully created symlink for Go version {}", version);
195
196        // 验证符号链接是否正确创建
197        if !current_path.exists() {
198            return Err("Symlink does not exist after creation".to_string());
199        }
200
201        let current_go_binary = current_path.join("bin").join("go");
202        if !current_go_binary.exists() {
203            return Err("Symlink target is invalid: missing go binary".to_string());
204        }
205
206        debug!("Successfully created symlink for Go version {}", version);
207        Ok(())
208    }
209    /// 获取当前活跃的Go版本
210    #[must_use]
211    pub fn get_current_version(&self, base_dir: &Path) -> Option<String> {
212        // 首先尝试从链接获取(跨平台支持junction和symlink)
213        if let Some(target) = self.get_link_target(base_dir) {
214            return target
215                .file_name()
216                .and_then(|name| name.to_str())
217                .map(std::string::ToString::to_string);
218        }
219
220        // 最后尝试从环境变量推断
221        if let Ok(goroot) = std::env::var("GOROOT") {
222            let goroot_path = PathBuf::from(goroot);
223            if goroot_path.starts_with(base_dir) {
224                return goroot_path
225                    .file_name()
226                    .and_then(|name| name.to_str())
227                    .map(std::string::ToString::to_string);
228            }
229        }
230
231        None
232    }
233    /// 获取链接指向的目标路径(跨平台)
234    #[must_use]
235    pub fn get_link_target(&self, base_dir: &Path) -> Option<PathBuf> {
236        let link_path = base_dir.join("current");
237
238        if !link_path.exists() {
239            return None;
240        }
241
242        // 检查是否为链接(适用于Windows junction和Unix symlink)
243        if link_path.is_symlink() {
244            // 使用标准库读取链接目标
245            if let Ok(target) = std::fs::read_link(&link_path) {
246                return Some(target);
247            }
248        }
249
250        None
251    }
252    /// 获取junction指向的目标路径(Windows特定,向后兼容)
253    #[cfg(target_os = "windows")]
254    #[must_use]
255    pub fn get_junction_target(&self, base_dir: &Path) -> Option<PathBuf> {
256        self.get_link_target(base_dir)
257    }
258    /// 获取符号链接指向的目标路径(跨平台)
259    #[must_use]
260    pub fn get_symlink_target(&self, base_dir: &Path) -> Option<PathBuf> {
261        self.get_link_target(base_dir)
262    }
263    /// 获取链接状态信息(跨平台)
264    #[must_use]
265    pub fn get_symlink_info(&self, base_dir: &Path) -> String {
266        let link_path = base_dir.join("current");
267
268        if !link_path.exists() {
269            #[cfg(target_os = "windows")]
270            return "No junction found".to_string();
271            #[cfg(not(target_os = "windows"))]
272            return "No symlink found".to_string();
273        }
274
275        if let Some(target) = self.get_link_target(base_dir) {
276            #[cfg(target_os = "windows")]
277            return format!("Junction: {} -> {}", link_path.display(), target.display());
278            #[cfg(not(target_os = "windows"))]
279            return format!("Symlink: {} -> {}", link_path.display(), target.display());
280        }
281
282        #[cfg(target_os = "windows")]
283        return "Junction exists but target unknown".to_string();
284        #[cfg(not(target_os = "windows"))]
285        return "Symlink exists but target unknown".to_string();
286    }
287
288    /// 解压 ZIP 文件 (Windows)
289    #[cfg(target_os = "windows")]
290    fn extract_zip(zip_path: &Path, extract_to: &Path) -> Result<(), String> {
291        use std::fs::File;
292        use std::io::BufReader;
293
294        let file = File::open(zip_path).map_err(|e| format!("Failed to open zip file: {e}"))?;
295        let reader = BufReader::new(file);
296
297        let mut archive =
298            zip::ZipArchive::new(reader).map_err(|e| format!("Failed to read zip archive: {e}"))?;
299
300        for i in 0..archive.len() {
301            let mut file = archive
302                .by_index(i)
303                .map_err(|e| format!("Failed to access file in archive: {e}"))?;
304            let Some(file_path) = file.enclosed_name() else { continue }; // Skip the top-level "go" directory and extract its contents directly
305            let Ok(relative_path) = file_path.strip_prefix("go") else {
306                continue; // Skip files not in the "go" directory
307            };
308
309            // Skip empty path (the "go" directory itself)
310            if relative_path.as_os_str().is_empty() {
311                continue;
312            }
313
314            let outpath = extract_to.join(relative_path);
315
316            if file.name().ends_with('/') {
317                // Directory
318                std::fs::create_dir_all(&outpath)
319                    .map_err(|e| format!("Failed to create directory: {e}"))?;
320            } else {
321                // File
322                if let Some(p) = outpath.parent() {
323                    if !p.exists() {
324                        std::fs::create_dir_all(p)
325                            .map_err(|e| format!("Failed to create parent directory: {e}"))?;
326                    }
327                }
328
329                let mut outfile = File::create(&outpath)
330                    .map_err(|e| format!("Failed to create output file: {e}"))?;
331
332                std::io::copy(&mut file, &mut outfile)
333                    .map_err(|e| format!("Failed to extract file: {e}"))?;
334            }
335        }
336
337        Ok(())
338    }
339
340    /// 解压 tar.gz 文件 (Unix)
341    #[cfg(not(target_os = "windows"))]
342    fn extract_tar_gz(&self, tar_gz_path: &Path, extract_to: &Path) -> Result<(), String> {
343        use flate2::read::GzDecoder;
344        use std::fs::File;
345        use tar::Archive;
346
347        let file =
348            File::open(tar_gz_path).map_err(|e| format!("Failed to open tar.gz file: {}", e))?;
349
350        let gz = GzDecoder::new(file);
351        let mut archive = Archive::new(gz);
352
353        // Extract all entries
354        for entry in
355            archive.entries().map_err(|e| format!("Failed to read archive entries: {}", e))?
356        {
357            let mut entry = entry.map_err(|e| format!("Failed to read archive entry: {}", e))?;
358
359            let entry_path =
360                entry.path().map_err(|e| format!("Failed to get entry path: {}", e))?;
361
362            // Skip the top-level "go" directory and extract its contents directly
363            let relative_path = if let Ok(stripped) = entry_path.strip_prefix("go") {
364                stripped
365            } else {
366                continue; // Skip files not in the "go" directory
367            };
368
369            // Skip empty path (the "go" directory itself)
370            if relative_path.as_os_str().is_empty() {
371                continue;
372            }
373
374            let target_path = extract_to.join(relative_path);
375
376            // Create parent directories if needed
377            if let Some(parent) = target_path.parent() {
378                std::fs::create_dir_all(parent)
379                    .map_err(|e| format!("Failed to create parent directory: {}", e))?;
380            }
381
382            // Extract the entry to the target path
383            entry.unpack(&target_path).map_err(|e| format!("Failed to extract entry: {}", e))?;
384        }
385
386        Ok(())
387    }
388    /// 计算文件的 SHA256 哈希值
389    ///
390    /// # Errors
391    ///
392    /// Returns an error if:
393    /// - The file cannot be opened
394    /// - File I/O operations fail during reading
395    /// - The hash calculation process fails
396    pub async fn calculate_file_hash(&self, file_path: &Path) -> Result<String, String> {
397        let mut file = tokio::fs::File::open(file_path)
398            .await
399            .map_err(|e| format!("Failed to open file for hash calculation: {e}"))?;
400
401        let mut hasher = Sha256::new();
402        let mut buffer = vec![0u8; 8192]; // 8KB 缓冲区
403
404        loop {
405            let bytes_read =
406                file.read(&mut buffer).await.map_err(|e| format!("Error reading file: {e}"))?;
407
408            if bytes_read == 0 {
409                break;
410            }
411
412            hasher.update(&buffer[..bytes_read]);
413        }
414
415        let result = hasher.finalize();
416        Ok(format!("{result:x}"))
417    }
418
419    /// 获取 Go 版本的官方 SHA256 校验和
420    async fn get_official_checksum(
421        &self,
422        version: &str,
423        os: &str,
424        arch: &str,
425        extension: &str,
426    ) -> Result<String, String> {
427        let client = reqwest::Client::new();
428        let checksums_url = "https://go.dev/dl/?mode=json&include=all".to_string();
429
430        debug!("Fetching official checksums: {checksums_url}");
431
432        let response = client
433            .get(&checksums_url)
434            .send()
435            .await
436            .map_err(|e| format!("Failed to fetch checksums: {e}"))?;
437
438        let versions: serde_json::Value =
439            response.json().await.map_err(|e| format!("Failed to parse checksum data: {e}"))?;
440
441        let filename = format!("go{version}.{os}-{arch}.{extension}");
442        debug!("Looking for checksum for file: {filename}");
443
444        // 查找对应版本和文件的校验和
445        if let Some(releases) = versions.as_array() {
446            for release in releases {
447                if let Some(version_str) = release.get("version").and_then(|v| v.as_str()) {
448                    if version_str == format!("go{version}") {
449                        if let Some(files) = release.get("files").and_then(|f| f.as_array()) {
450                            for file in files {
451                                if let Some(file_name) =
452                                    file.get("filename").and_then(|f| f.as_str())
453                                {
454                                    if file_name == filename {
455                                        if let Some(sha256) =
456                                            file.get("sha256").and_then(|s| s.as_str())
457                                        {
458                                            debug!("Found official checksum: {sha256}");
459                                            return Ok(sha256.to_string());
460                                        }
461                                    }
462                                }
463                            }
464                        }
465                    }
466                }
467            }
468        }
469
470        Err(format!("Official checksum not found for Go {version} ({filename})"))
471    }
472
473    /// 校验下载文件的完整性
474    async fn verify_file_integrity(
475        &self,
476        file_path: &Path,
477        version: &str,
478        os: &str,
479        arch: &str,
480        extension: &str,
481    ) -> Result<(), String> {
482        debug!("Starting file integrity verification: {}", file_path.display());
483
484        // 计算下载文件的哈希值
485        let file_hash = self.calculate_file_hash(file_path).await?;
486        debug!("File hash: {file_hash}");
487
488        // 获取官方校验和
489        let official_hash = self.get_official_checksum(version, os, arch, extension).await?;
490        debug!("Official hash: {official_hash}");
491
492        // 比较哈希值
493        if file_hash.to_lowercase() == official_hash.to_lowercase() {
494            info!("File integrity verification passed");
495            Ok(())
496        } else {
497            Err(format!(
498                "File integrity verification failed!\nExpected: {official_hash}\nActual: {file_hash}"
499            ))
500        }
501    }
502    /// 验证缓存文件的完整性
503    /// 检查文件是否存在、非空,并且可以被读取
504    #[must_use]
505    pub fn validate_cache_file(&self, file_path: &Path) -> bool {
506        if !file_path.exists() {
507            return false;
508        }
509
510        match std::fs::metadata(file_path) {
511            Ok(metadata) => {
512                // 文件大小检查:至少应该有一些内容(比如大于1KB)
513                if metadata.len() < 1024 {
514                    return false;
515                }
516
517                // 尝试打开文件以确保它可读
518                std::fs::File::open(file_path).is_ok()
519            }
520            Err(_) => false,
521        }
522    }
523    /// 获取 Go 版本的详细信息
524    ///
525    /// # Errors
526    ///
527    /// Returns an error if:
528    /// - Invalid version format
529    /// - Network request fails
530    /// - Version not available for the current platform
531    /// - File size cannot be determined
532    pub async fn get_version_info(
533        &self,
534        version: &str,
535        install_dir: &Path,
536        cache_dir: &Path,
537    ) -> Result<GoVersionInfo, String> {
538        use crate::downloader::Downloader;
539
540        // 确定当前平台信息
541        let (os, arch) = if cfg!(target_os = "windows") {
542            ("windows", if cfg!(target_arch = "x86_64") { "amd64" } else { "386" })
543        } else if cfg!(target_os = "macos") {
544            ("darwin", if cfg!(target_arch = "x86_64") { "amd64" } else { "arm64" })
545        } else {
546            ("linux", if cfg!(target_arch = "x86_64") { "amd64" } else { "386" })
547        };
548
549        let extension = if cfg!(target_os = "windows") { "zip" } else { "tar.gz" };
550        let filename = format!("go{version}.{os}-{arch}.{extension}");
551        let download_url = format!("https://go.dev/dl/{filename}");
552
553        // 检查是否已安装
554        let install_path = install_dir.join(version);
555        let is_installed = install_path.exists() && {
556            let go_binary = if cfg!(target_os = "windows") {
557                install_path.join("bin").join("go.exe")
558            } else {
559                install_path.join("bin").join("go")
560            };
561            go_binary.exists()
562        };
563
564        // 检查是否已缓存
565        let cache_path = cache_dir.join(&filename);
566        let is_cached = cache_path.exists() && self.validate_cache_file(&cache_path);
567
568        // 获取官方校验和
569        let sha256 = self.get_official_checksum(version, os, arch, extension).await.ok();
570
571        // 获取文件大小
572        let size = if is_cached {
573            // 如果已缓存,从本地文件获取大小
574            debug!("Getting size from cached file: {}", cache_path.display());
575            std::fs::metadata(&cache_path).ok().map(|m| m.len())
576        } else {
577            // 如果未缓存,尝试通过网络获取文件大小
578            debug!("Getting size from network for: {download_url}");
579            let downloader = Downloader::new();
580            match downloader.get_file_size(&download_url).await {
581                Ok(size) => {
582                    debug!("Successfully got file size from network: {size} bytes");
583                    Some(size)
584                }
585                Err(e) => {
586                    debug!("Failed to get file size from network: {e}");
587                    None
588                }
589            }
590        };
591
592        Ok(GoVersionInfo {
593            version: version.to_string(),
594            os: os.to_string(),
595            arch: arch.to_string(),
596            extension: extension.to_string(),
597            filename,
598            download_url,
599            sha256,
600            size,
601            is_installed,
602            is_cached,
603            install_path: if is_installed { Some(install_path) } else { None },
604            cache_path: if is_cached { Some(cache_path) } else { None },
605        })
606    }
607}
608
609#[async_trait::async_trait]
610impl VersionManager for GoManager {
611    /// 安装指定版本的Go
612    #[allow(clippy::too_many_lines)]
613    async fn install(&self, request: InstallRequest) -> Result<VersionInfo, String> {
614        let version = &request.version;
615        let install_dir = &request.install_dir;
616        let download_dir = &request.download_dir;
617        let force = request.force;
618
619        // Check if install directory exists and is accessible
620        if !install_dir.exists() {
621            return Err("Install directory does not exist".to_string());
622        }
623
624        if !install_dir.is_dir() {
625            return Err("Install path is not a directory".to_string());
626        }
627
628        // 创建版本目录
629        let version_dir = install_dir.join(version);
630        if version_dir.exists() && !force {
631            return Err(format!("Go version {version} is already installed"));
632        }
633
634        // 如果强制安装且版本目录存在,删除现有目录
635        if force && version_dir.exists() {
636            std::fs::remove_dir_all(&version_dir)
637                .map_err(|e| format!("Failed to remove existing version directory: {e}"))?;
638        }
639
640        std::fs::create_dir_all(&version_dir)
641            .map_err(|e| format!("Failed to create version directory: {e}"))?;
642
643        // 构建下载URL
644        let (os, arch) = if cfg!(target_os = "windows") {
645            ("windows", if cfg!(target_arch = "x86_64") { "amd64" } else { "386" })
646        } else if cfg!(target_os = "macos") {
647            ("darwin", if cfg!(target_arch = "x86_64") { "amd64" } else { "arm64" })
648        } else {
649            ("linux", if cfg!(target_arch = "x86_64") { "amd64" } else { "386" })
650        };
651
652        let extension = if cfg!(target_os = "windows") { "zip" } else { "tar.gz" };
653        let download_url = format!("https://go.dev/dl/go{version}.{os}-{arch}.{extension}");
654
655        // 设置下载文件名和路径
656        let archive_name = format!("go{version}.{os}-{arch}.{extension}");
657
658        // 验证下载目录存在
659        if !download_dir.exists() {
660            return Err(format!(
661                "Download directory does not exist: {}. Please ensure the download directory is created before installation.",
662                download_dir.display()
663            ));
664        }
665
666        if !download_dir.is_dir() {
667            return Err(format!("Download path is not a directory: {}", download_dir.display()));
668        }
669
670        let download_path = download_dir.join(&archive_name);
671
672        // 检查缓存文件是否已存在
673        let need_download = if force {
674            // 强制模式:删除现有缓存文件并重新下载
675            if download_path.exists() {
676                debug!("Force mode: removing existing cached file");
677                std::fs::remove_file(&download_path)
678                    .map_err(|e| format!("Failed to remove cached file: {e}"))?;
679            }
680            debug!("Force mode: downloading Go {version} from {download_url}");
681            true
682        } else if download_path.exists() {
683            debug!("Found cached file: {}", download_path.display());
684
685            // 使用更严格的完整性验证
686            if self.validate_cache_file(&download_path) {
687                let metadata = std::fs::metadata(&download_path).unwrap();
688                debug!("Using valid cached file (size: {} bytes)", metadata.len());
689                false
690            } else {
691                debug!("Cached file appears to be corrupted or incomplete, will re-download");
692                true
693            }
694        } else {
695            debug!("Downloading Go {version} from {download_url}");
696            true
697        };
698
699        // 只有在需要时才下载文件
700        if need_download {
701            // 使用内置下载器下载
702            let downloader = Downloader::new();
703
704            // 先获取文件大小,然后创建正确大小的进度报告器
705            let file_size =
706                downloader.get_file_size(&download_url).await.map_err(|e| format!("{e}"))?;
707            let progress_reporter = ProgressReporter::new(file_size);
708
709            downloader
710                .download(&download_url, &download_path, Some(progress_reporter))
711                .await
712                .map_err(|e| format!("{e}"))?;
713
714            // 下载完成后立即校验文件完整性
715            debug!("Verifying downloaded file integrity");
716            self.verify_file_integrity(&download_path, version, os, arch, extension)
717                .await
718                .map_err(|e| {
719                    // 校验失败时删除损坏的文件
720                    if download_path.exists() {
721                        let _ = std::fs::remove_file(&download_path);
722                        warn!(
723                            "Verification failed, deleted corrupted file: {}",
724                            download_path.display()
725                        );
726                    }
727                    format!("File integrity verification failed: {e}")
728                })?;
729        } else {
730            // 即使是缓存文件,也要进行完整性校验
731            debug!("Verifying cached file integrity");
732            self.verify_file_integrity(&download_path, version, os, arch, extension)
733                .await
734                .map_err(|e| {
735                    // 校验失败时删除损坏的缓存文件
736                    if download_path.exists() {
737                        let _ = std::fs::remove_file(&download_path);
738                        warn!(
739                            "Cached file verification failed, deleted: {}",
740                            download_path.display()
741                        );
742                    }
743                    format!(
744                        "Cached file integrity verification failed, recommend re-downloading: {e}"
745                    )
746                })?;
747        }
748
749        debug!("Extracting Go {} to {}", version, version_dir.display());
750
751        // 解压下载的文件
752        self.extract_archive(&download_path, &version_dir)?;
753
754        // 如果使用下载目录,下载完成后可以选择保留或删除压缩包
755        // 这里我们保留压缩包以便下次使用(由调用方决定缓存策略)
756
757        info!("Go {} installed successfully", version);
758
759        // Return version info
760        Ok(VersionInfo { version: version.to_string(), install_path: version_dir })
761    }
762    /// 切换到指定版本
763    fn switch_to(&self, request: SwitchRequest) -> Result<(), String> {
764        let version = &request.version;
765        let base_dir = &request.base_dir;
766        self.switch_version(version, base_dir)
767    }
768
769    /// 卸载指定版本
770    fn uninstall(&self, request: UninstallRequest) -> Result<(), String> {
771        let version = &request.version;
772        let base_dir = &request.base_dir;
773        let version_path = base_dir.join(version);
774
775        if !version_path.exists() {
776            return Err(format!("Go version {version} is not installed"));
777        }
778
779        // 检查要卸载的版本是否为当前活跃版本
780        if let Some(current_version) = self.get_current_version(base_dir) {
781            if current_version == *version {
782                return Err(format!(
783                    "Cannot uninstall Go {version} as it is currently active. Please switch to another version or clear the current symlink first."
784                ));
785            }
786        }
787
788        std::fs::remove_dir_all(&version_path)
789            .map_err(|e| format!("Failed to remove Go {version}: {e}"))?;
790
791        Ok(())
792    }
793
794    /// 列出已安装的版本
795    ///
796    /// 扫描基础目录中的所有子目录,返回包含有效Go安装的版本目录列表。
797    /// 自动排除:
798    /// - `current` 目录(junction point)
799    /// - 没有 `bin/go` 或 `bin/go.exe` 的目录
800    /// - 非目录文件
801    ///
802    /// 返回的版本列表按字母顺序排序。
803    fn list_installed(&self, request: ListInstalledRequest) -> Result<VersionList, String> {
804        let base_dir = &request.base_dir;
805        if !base_dir.exists() {
806            return Err(format!("Base directory does not exist: {}", base_dir.display()));
807        }
808
809        let mut versions = Vec::new();
810
811        match std::fs::read_dir(base_dir) {
812            Ok(entries) => {
813                for entry in entries.flatten() {
814                    let name = entry.file_name().to_string_lossy().to_string();
815
816                    // 跳过current目录(junction point)
817                    if name == "current" {
818                        continue;
819                    }
820
821                    // 检查是否为目录且包含Go二进制文件
822                    if entry.path().is_dir() {
823                        let go_binary_name =
824                            if cfg!(target_os = "windows") { "go.exe" } else { "go" };
825                        let go_binary = entry.path().join("bin").join(go_binary_name);
826                        if go_binary.exists() {
827                            versions.push(name);
828                        }
829                    }
830                }
831            }
832            Err(e) => return Err(format!("Failed to read directory: {e}")),
833        }
834
835        versions.sort();
836        let total_count = versions.len();
837
838        Ok(VersionList { versions, total_count })
839    }
840
841    /// 列出可用的版本(实时获取)
842    async fn list_available(&self) -> Result<VersionList, String> {
843        // 从Go官方API获取版本列表
844        let url = "https://go.dev/dl/?mode=json";
845
846        let resp = reqwest::get(url).await.map_err(|e| format!("{e}"))?;
847        let releases: serde_json::Value = resp.json().await.map_err(|e| format!("{e}"))?;
848
849        let mut versions = Vec::new();
850
851        if let Some(array) = releases.as_array() {
852            for release in array {
853                if let Some(version_str) = release["version"].as_str() {
854                    // 去掉 "go" 前缀
855                    if let Some(version) = version_str.strip_prefix("go") {
856                        versions.push(version.to_string());
857                    }
858                }
859            }
860        }
861
862        // 只返回稳定版本,过滤掉 beta 和 rc 版本
863        versions.retain(|v| !v.contains("beta") && !v.contains("rc"));
864
865        // 倒序排列,最新版本在前
866        versions.sort_by(|a, b| {
867            // 简单的版本比较
868            let a_parts: Vec<u32> = a.split('.').filter_map(|s| s.parse().ok()).collect();
869            let b_parts: Vec<u32> = b.split('.').filter_map(|s| s.parse().ok()).collect();
870            b_parts.cmp(&a_parts)
871        });
872
873        let total_count = versions.len();
874
875        Ok(VersionList { versions, total_count })
876    }
877
878    /// 获取当前状态
879    fn status(&self, request: StatusRequest) -> Result<RuntimeStatus, String> {
880        let base_dir = request.base_dir.as_deref();
881        let mut environment_vars = HashMap::new();
882
883        let goroot = std::env::var("GOROOT").unwrap_or_else(|_| "Not set".to_string());
884        let gopath = std::env::var("GOPATH").unwrap_or_else(|_| "Not set".to_string());
885
886        environment_vars.insert("GOROOT".to_string(), goroot.clone());
887        environment_vars.insert("GOPATH".to_string(), gopath);
888        let mut current_version = None;
889        let mut install_path = None;
890        #[cfg(target_os = "windows")]
891        let mut link_info = None;
892        #[cfg(not(target_os = "windows"))]
893        let link_info = None;
894
895        if let Some(base_dir) = base_dir {
896            current_version = self.get_current_version(base_dir);
897
898            if let Some(ref version) = current_version {
899                install_path = Some(base_dir.join(version));
900            }
901            #[cfg(target_os = "windows")]
902            {
903                link_info = Some(self.get_symlink_info(base_dir));
904            }
905        }
906
907        Ok(RuntimeStatus { current_version, install_path, environment_vars, link_info })
908    }
909}