11use crate :: env_manager:: {
2- DownloadStatus , EnvironmentProvider , EnvironmentVersion , download_with_fallback,
3- emit_download_progress,
2+ DownloadStatus , EnvironmentProvider , EnvironmentVersion , emit_download_progress,
43} ;
54use log:: { error, info, warn} ;
65use serde:: { Deserialize , Serialize } ;
@@ -23,6 +22,25 @@ struct GithubAsset {
2322 size : u64 ,
2423}
2524
25+ // CDN Metadata 结构
26+ #[ derive( Debug , Deserialize , Serialize , Clone ) ]
27+ struct MetadataRelease {
28+ version : String , // 版本号,如 "1.11.1.1262"
29+ display_name : String , // 显示名称,如 "Clojure 1.11.1.1262"
30+ published_at : String , // 发布时间
31+ download_url : String , // CDN 下载地址
32+ github_url : String , // GitHub 官方下载地址(作为备用)
33+ file_name : String , // 文件名,如 "clojure-tools-1.11.1.1262.tar.gz"
34+ size : u64 , // 文件大小(字节)
35+ supported_platforms : Vec < String > , // 支持的平台,如 ["macos", "linux", "windows"]
36+ }
37+
38+ #[ derive( Debug , Deserialize , Serialize ) ]
39+ struct Metadata {
40+ language : String , // 语言名称 "clojure"
41+ releases : Vec < MetadataRelease > ,
42+ }
43+
2644#[ derive( Debug , Deserialize , Serialize ) ]
2745struct CachedReleases {
2846 releases : Vec < GithubRelease > ,
@@ -108,6 +126,115 @@ impl ClojureEnvironmentProvider {
108126 "clojure-tools-"
109127 }
110128
129+ // 获取当前系统平台
130+ fn get_current_platform ( ) -> & ' static str {
131+ if cfg ! ( target_os = "macos" ) {
132+ "macos"
133+ } else if cfg ! ( target_os = "linux" ) {
134+ "linux"
135+ } else if cfg ! ( target_os = "windows" ) {
136+ "windows"
137+ } else {
138+ "unknown"
139+ }
140+ }
141+
142+ // 将 metadata 转换为 EnvironmentVersion 列表
143+ fn parse_metadata_to_versions (
144+ & self ,
145+ metadata : Metadata ,
146+ ) -> Result < Vec < EnvironmentVersion > , String > {
147+ let current_platform = Self :: get_current_platform ( ) ;
148+ let mut versions = Vec :: new ( ) ;
149+
150+ for release in metadata. releases {
151+ // 检查是否支持当前平台
152+ if !release
153+ . supported_platforms
154+ . contains ( & current_platform. to_string ( ) )
155+ {
156+ continue ;
157+ }
158+
159+ let is_installed = self . is_version_installed ( & release. version ) ;
160+ let install_path = if is_installed {
161+ Some (
162+ self . get_version_install_path ( & release. version )
163+ . to_string_lossy ( )
164+ . to_string ( ) ,
165+ )
166+ } else {
167+ None
168+ } ;
169+
170+ versions. push ( EnvironmentVersion {
171+ version : release. version . clone ( ) ,
172+ download_url : release. download_url . clone ( ) , // 直接使用 metadata 中的 CDN 下载地址
173+ fallback_url : Some ( release. github_url . clone ( ) ) , // 保存 GitHub URL 作为备用
174+ install_path,
175+ is_installed,
176+ size : Some ( release. size ) ,
177+ release_date : Some ( release. published_at . clone ( ) ) ,
178+ } ) ;
179+ }
180+
181+ if versions. is_empty ( ) {
182+ return Err ( format ! ( "没有找到支持 {} 平台的版本" , current_platform) ) ;
183+ }
184+
185+ Ok ( versions)
186+ }
187+
188+ // 从 CDN 获取 metadata.json
189+ async fn fetch_metadata_from_cdn ( & self ) -> Result < Metadata , String > {
190+ use crate :: config:: get_app_config_internal;
191+
192+ let config = get_app_config_internal ( ) . map_err ( |e| format ! ( "读取配置失败: {}" , e) ) ?;
193+
194+ let cdn_enabled = config
195+ . environment_mirror
196+ . as_ref ( )
197+ . and_then ( |m| m. enabled )
198+ . unwrap_or ( false ) ;
199+
200+ if !cdn_enabled {
201+ return Err ( "CDN 未启用" . to_string ( ) ) ;
202+ }
203+
204+ let base_url = config
205+ . environment_mirror
206+ . as_ref ( )
207+ . and_then ( |m| m. base_url . as_ref ( ) )
208+ . ok_or ( "CDN 地址未配置" ) ?;
209+
210+ let metadata_url = format ! ( "{}/clojure/metadata.json" , base_url) ;
211+ info ! ( "从 CDN 获取 Clojure metadata: {}" , metadata_url) ;
212+
213+ let client = reqwest:: Client :: builder ( )
214+ . user_agent ( "CodeForge" )
215+ . timeout ( Duration :: from_secs ( 30 ) )
216+ . build ( )
217+ . map_err ( |e| format ! ( "创建 HTTP 客户端失败: {}" , e) ) ?;
218+
219+ let response = client
220+ . get ( & metadata_url)
221+ . send ( )
222+ . await
223+ . map_err ( |e| format ! ( "请求 CDN metadata 失败: {}" , e) ) ?;
224+
225+ if !response. status ( ) . is_success ( ) {
226+ return Err ( format ! ( "CDN 返回错误状态码: {}" , response. status( ) ) ) ;
227+ }
228+
229+ let metadata: Metadata = response
230+ . json ( )
231+ . await
232+ . map_err ( |e| format ! ( "解析 metadata.json 失败: {}" , e) ) ?;
233+
234+ info ! ( "成功从 CDN 获取 {} 个版本" , metadata. releases. len( ) ) ;
235+ Ok ( metadata)
236+ }
237+
111238 async fn fetch_github_releases ( & self ) -> Result < Vec < GithubRelease > , String > {
112239 if let Some ( cached_releases) = self . read_cache ( ) {
113240 return Ok ( cached_releases) ;
@@ -224,18 +351,78 @@ impl ClojureEnvironmentProvider {
224351 async fn download_file (
225352 & self ,
226353 url : & str ,
354+ fallback_url : Option < & String > ,
227355 dest : & PathBuf ,
228356 app_handle : AppHandle ,
229357 version : & str ,
230358 ) -> Result < ( ) , String > {
359+ use crate :: config:: get_app_config_internal;
360+
231361 info ! ( "开始下载: {} -> {}" , url, dest. display( ) ) ;
232362
233363 let client = reqwest:: Client :: builder ( )
234364 . user_agent ( "CodeForge" )
235365 . build ( )
236366 . map_err ( |e| format ! ( "创建 HTTP 客户端失败: {}" , e) ) ?;
237367
238- let response = download_with_fallback ( & client, url, "clojure" , version) . await ?;
368+ // 尝试从主 URL 下载
369+ let response = match client. get ( url) . send ( ) . await {
370+ Ok ( resp) if resp. status ( ) . is_success ( ) => {
371+ info ! ( "下载成功" ) ;
372+ resp
373+ }
374+ Ok ( resp) => {
375+ let status = resp. status ( ) ;
376+ warn ! ( "下载失败: HTTP {}" , status) ;
377+
378+ // 检查是否启用 fallback 且有 fallback URL
379+ if let Some ( fb_url) = fallback_url {
380+ let fallback_enabled = get_app_config_internal ( )
381+ . ok ( )
382+ . and_then ( |config| config. environment_mirror )
383+ . and_then ( |mirror| mirror. fallback_enabled )
384+ . unwrap_or ( false ) ;
385+
386+ if fallback_enabled {
387+ info ! ( "尝试使用备用 URL: {}" , fb_url) ;
388+ client
389+ . get ( fb_url)
390+ . send ( )
391+ . await
392+ . map_err ( |e| format ! ( "备用 URL 下载失败: {}" , e) ) ?
393+ } else {
394+ return Err ( format ! ( "下载失败 (HTTP {}), 未启用自动回退" , status) ) ;
395+ }
396+ } else {
397+ return Err ( format ! ( "下载失败: HTTP {}" , status) ) ;
398+ }
399+ }
400+ Err ( e) => {
401+ warn ! ( "下载失败: {}" , e) ;
402+
403+ // 检查是否启用 fallback 且有 fallback URL
404+ if let Some ( fb_url) = fallback_url {
405+ let fallback_enabled = get_app_config_internal ( )
406+ . ok ( )
407+ . and_then ( |config| config. environment_mirror )
408+ . and_then ( |mirror| mirror. fallback_enabled )
409+ . unwrap_or ( false ) ;
410+
411+ if fallback_enabled {
412+ info ! ( "尝试使用备用 URL: {}" , fb_url) ;
413+ client
414+ . get ( fb_url)
415+ . send ( )
416+ . await
417+ . map_err ( |e| format ! ( "备用 URL 下载失败: {}" , e) ) ?
418+ } else {
419+ return Err ( format ! ( "下载失败 ({}), 未启用自动回退" , e) ) ;
420+ }
421+ } else {
422+ return Err ( format ! ( "下载失败: {}" , e) ) ;
423+ }
424+ }
425+ } ;
239426
240427 if !response. status ( ) . is_success ( ) {
241428 return Err ( format ! ( "下载失败: HTTP {}" , response. status( ) ) ) ;
@@ -333,11 +520,7 @@ impl ClojureEnvironmentProvider {
333520 }
334521
335522 // 组织 Clojure 安装目录结构
336- fn organize_installation (
337- & self ,
338- temp_dir : & Path ,
339- install_path : & Path ,
340- ) -> Result < ( ) , String > {
523+ fn organize_installation ( & self , temp_dir : & Path , install_path : & Path ) -> Result < ( ) , String > {
341524 std:: fs:: create_dir_all ( install_path) . map_err ( |e| format ! ( "创建安装目录失败: {}" , e) ) ?;
342525
343526 let tools_dir = temp_dir. join ( "clojure-tools" ) ;
@@ -448,6 +631,32 @@ impl EnvironmentProvider for ClojureEnvironmentProvider {
448631 }
449632
450633 async fn fetch_available_versions ( & self ) -> Result < Vec < EnvironmentVersion > , String > {
634+ use crate :: config:: get_app_config_internal;
635+
636+ // 优先尝试从 CDN 获取 metadata.json
637+ match self . fetch_metadata_from_cdn ( ) . await {
638+ Ok ( metadata) => {
639+ info ! ( "使用 CDN metadata 获取版本列表" ) ;
640+ return self . parse_metadata_to_versions ( metadata) ;
641+ }
642+ Err ( e) => {
643+ warn ! ( "CDN metadata 获取失败: {}" , e) ;
644+
645+ // 检查是否启用 fallback
646+ let fallback_enabled = get_app_config_internal ( )
647+ . ok ( )
648+ . and_then ( |config| config. environment_mirror )
649+ . and_then ( |mirror| mirror. fallback_enabled )
650+ . unwrap_or ( false ) ;
651+
652+ if !fallback_enabled {
653+ return Err ( format ! ( "CDN metadata 获取失败,未启用自动回退: {}" , e) ) ;
654+ }
655+
656+ info ! ( "fallback 已启用,回退到 GitHub API" ) ;
657+ }
658+ }
659+
451660 let releases = self . fetch_github_releases ( ) . await ?;
452661 let pattern = Self :: get_download_pattern ( ) ;
453662
@@ -471,6 +680,7 @@ impl EnvironmentProvider for ClojureEnvironmentProvider {
471680 versions. push ( EnvironmentVersion {
472681 version : version. clone ( ) ,
473682 download_url : asset. browser_download_url . clone ( ) ,
683+ fallback_url : None , // GitHub API 获取的版本没有 CDN URL,所以不需要 fallback
474684 install_path,
475685 is_installed,
476686 size : Some ( asset. size ) ,
@@ -505,6 +715,7 @@ impl EnvironmentProvider for ClojureEnvironmentProvider {
505715 installed. push ( EnvironmentVersion {
506716 version : version. clone ( ) ,
507717 download_url : String :: new ( ) ,
718+ fallback_url : None ,
508719 install_path : Some ( path. to_string_lossy ( ) . to_string ( ) ) ,
509720 is_installed : true ,
510721 size : None ,
@@ -544,14 +755,21 @@ impl EnvironmentProvider for ClojureEnvironmentProvider {
544755 . ok_or_else ( || format ! ( "未找到版本: {}" , version) ) ?;
545756
546757 let download_url = & version_info. download_url ;
758+ let fallback_url = version_info. fallback_url . as_ref ( ) ;
547759 let file_name = download_url
548760 . split ( '/' )
549761 . last ( )
550762 . ok_or_else ( || "无效的下载 URL" . to_string ( ) ) ?;
551763 let temp_file = std:: env:: temp_dir ( ) . join ( file_name) ;
552764
553- self . download_file ( download_url, & temp_file, app_handle. clone ( ) , version)
554- . await ?;
765+ self . download_file (
766+ download_url,
767+ fallback_url,
768+ & temp_file,
769+ app_handle. clone ( ) ,
770+ version,
771+ )
772+ . await ?;
555773
556774 let install_path = self . get_version_install_path ( version) ;
557775 let temp_extract_dir = std:: env:: temp_dir ( ) . join ( format ! ( "clojure-tools-{}" , version) ) ;
0 commit comments