Skip to content

Commit 68b1138

Browse files
committed
feat: 支持网络回滚
1 parent 28e3174 commit 68b1138

5 files changed

Lines changed: 302 additions & 17 deletions

File tree

src-tauri/src/config.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub struct EditorConfig {
2626
pub struct EnvironmentMirrorConfig {
2727
pub enabled: Option<bool>,
2828
pub base_url: Option<String>,
29+
pub fallback_enabled: Option<bool>,
2930
}
3031

3132
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -60,6 +61,7 @@ impl Default for AppConfig {
6061
environment_mirror: Some(EnvironmentMirrorConfig {
6162
enabled: Some(true),
6263
base_url: Some("http://cdn.global.devlive.top".to_string()),
64+
fallback_enabled: Some(false),
6365
}),
6466
}
6567
}
@@ -129,6 +131,7 @@ impl ConfigManager {
129131
config.environment_mirror = Some(EnvironmentMirrorConfig {
130132
enabled: Some(true),
131133
base_url: Some("http://cdn.global.devlive.top".to_string()),
134+
fallback_enabled: Some(false),
132135
});
133136
println!("读取配置 -> 添加默认 environment_mirror 配置");
134137
}
@@ -241,6 +244,7 @@ impl ConfigManager {
241244
environment_mirror: Some(EnvironmentMirrorConfig {
242245
enabled: Some(true),
243246
base_url: Some("http://cdn.global.devlive.top".to_string()),
247+
fallback_enabled: Some(false),
244248
}),
245249
}
246250
}

src-tauri/src/env_manager.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use tauri::{AppHandle, Emitter};
1010
pub struct EnvironmentVersion {
1111
pub version: String,
1212
pub download_url: String,
13+
pub fallback_url: Option<String>, // 备用下载地址(如 GitHub URL)
1314
pub install_path: Option<String>,
1415
pub is_installed: bool,
1516
pub size: Option<u64>,
@@ -223,6 +224,15 @@ pub async fn download_with_fallback(
223224
language: &str,
224225
version: &str,
225226
) -> Result<reqwest::Response, String> {
227+
use crate::config::get_app_config_internal;
228+
229+
// 检查是否启用自动回退
230+
let fallback_enabled = get_app_config_internal()
231+
.ok()
232+
.and_then(|config| config.environment_mirror)
233+
.and_then(|mirror| mirror.fallback_enabled)
234+
.unwrap_or(false);
235+
226236
// 首先尝试从 CDN 下载
227237
match convert_to_cdn_url(original_url, language, version) {
228238
Ok(cdn_url) if cdn_url != original_url => {
@@ -233,10 +243,19 @@ pub async fn download_with_fallback(
233243
return Ok(response);
234244
}
235245
Ok(response) => {
236-
info!("CDN 下载失败 (HTTP {}), 回退到原始 URL", response.status());
246+
let status = response.status();
247+
if fallback_enabled {
248+
info!("CDN 下载失败 (HTTP {}), 回退到原始 URL", status);
249+
} else {
250+
return Err(format!("CDN 下载失败 (HTTP {}), 未启用自动回退", status));
251+
}
237252
}
238253
Err(e) => {
239-
info!("CDN 下载失败 ({}), 回退到原始 URL", e);
254+
if fallback_enabled {
255+
info!("CDN 下载失败 ({}), 回退到原始 URL", e);
256+
} else {
257+
return Err(format!("CDN 下载失败 ({}), 未启用自动回退", e));
258+
}
240259
}
241260
}
242261
}

src-tauri/src/env_providers/clojure.rs

Lines changed: 228 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use crate::env_manager::{
2-
DownloadStatus, EnvironmentProvider, EnvironmentVersion, download_with_fallback,
3-
emit_download_progress,
2+
DownloadStatus, EnvironmentProvider, EnvironmentVersion, emit_download_progress,
43
};
54
use log::{error, info, warn};
65
use 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)]
2745
struct 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

Comments
 (0)