tidepool_version_manager/
go.rs

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