fix(update): retry core downloads across available sources
This commit is contained in:
1 parent
594ab43b6a
commit
ccbe3a5625
4 files changed
+163
-11
No files matched your search
@@ -117,8 +117,10 @@ pub(crate) async fn install_core_version(
|
||||
Ok(was_running) => (
|
||||
was_running,
|
||||
install_core_version_inner(
|
||||
&app,
|
||||
&window,
|
||||
state.inner(),
|
||||
gui_config_state.inner(),
|
||||
token,
|
||||
version,
|
||||
&proxy_url,
|
||||
@@ -280,8 +282,10 @@ pub(crate) fn restart_core_process_with_state(
|
||||
}
|
||||
|
||||
pub(crate) async fn install_core_version_inner(
|
||||
app: &tauri::AppHandle,
|
||||
window: &tauri::Window,
|
||||
state: &CoreDownloadState,
|
||||
gui_config_state: &GuiConfigState,
|
||||
token: CancellationToken,
|
||||
version: Option<String>,
|
||||
proxy_url: &str,
|
||||
@@ -291,7 +295,8 @@ pub(crate) async fn install_core_version_inner(
|
||||
let platform = current_core_platform()?;
|
||||
let client = http_client(proxy_url, &custom_mirrors)?;
|
||||
state.progress(window, "检查版本", 0, None, true);
|
||||
let (release, _) = fetch_release_cancelable(
|
||||
let requested_source = download_source.clone();
|
||||
let (release, resolved_source) = fetch_release_cancelable(
|
||||
&client,
|
||||
version.as_deref(),
|
||||
&token,
|
||||
@@ -300,6 +305,13 @@ pub(crate) async fn install_core_version_inner(
|
||||
)
|
||||
.await?;
|
||||
let asset = select_release_asset(&release, &platform)?;
|
||||
let download_candidates = core_download_candidates(
|
||||
&release.tag_name,
|
||||
asset,
|
||||
resolved_source,
|
||||
configured_gitcode_core_repository(),
|
||||
&custom_mirrors,
|
||||
);
|
||||
|
||||
let install_dir = core_install_dir()?;
|
||||
let base_dir = core_base_dir()?;
|
||||
@@ -319,8 +331,23 @@ pub(crate) async fn install_core_version_inner(
|
||||
.ok_or_else(|| format!("非法 asset 文件名: {}", asset.name))?;
|
||||
let archive_path = download_dir.join(archive_file_name);
|
||||
|
||||
let downloaded = download_asset(&client, asset, &archive_path, window, state, &token).await?;
|
||||
let (downloaded, successful_source) = download_asset(
|
||||
&client,
|
||||
asset,
|
||||
&download_candidates,
|
||||
&archive_path,
|
||||
window,
|
||||
state,
|
||||
&token,
|
||||
)
|
||||
.await?;
|
||||
validate_downloaded_asset(asset, &downloaded)?;
|
||||
persist_automatic_download_source_switch(
|
||||
app,
|
||||
gui_config_state,
|
||||
&requested_source,
|
||||
&successful_source,
|
||||
)?;
|
||||
|
||||
ensure_not_cancelled(&token, Some(&archive_path))?;
|
||||
state.progress(
|
||||
@@ -660,6 +687,46 @@ pub(crate) fn release_from_tag_for_repositories(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn core_download_candidates(
|
||||
tag: &str,
|
||||
asset: &GithubAsset,
|
||||
preferred: VersionDownloadCandidate,
|
||||
gitcode_repository: Option<&str>,
|
||||
custom_mirrors: &[String],
|
||||
) -> Vec<(VersionDownloadCandidate, String)> {
|
||||
let tag = normalize_version(tag);
|
||||
let generated_github_url = format!("{RELEASE_DOWNLOAD_PREFIX}{tag}/{}", asset.name);
|
||||
let provided_urls = std::iter::once(&asset.browser_download_url)
|
||||
.chain(asset.fallback_download_urls.iter())
|
||||
.collect::<Vec<_>>();
|
||||
let github_url = provided_urls
|
||||
.iter()
|
||||
.find(|url| core_download_source_name(url) == "GitHub")
|
||||
.map(|url| (*url).clone())
|
||||
.unwrap_or(generated_github_url);
|
||||
let gitcode_url = provided_urls
|
||||
.iter()
|
||||
.find(|url| core_download_source_name(url) == "GitCode")
|
||||
.map(|url| (*url).clone())
|
||||
.or_else(|| {
|
||||
gitcode_repository
|
||||
.map(|repository| gitcode_release_attachment_url(repository, &tag, &asset.name))
|
||||
});
|
||||
version_download_source_candidates(preferred, gitcode_url.is_some(), custom_mirrors)
|
||||
.into_iter()
|
||||
.filter_map(|candidate| {
|
||||
let url = match candidate.source {
|
||||
VersionDownloadSource::Github => github_url.clone(),
|
||||
VersionDownloadSource::Gitcode => gitcode_url.clone()?,
|
||||
VersionDownloadSource::GhProxy
|
||||
| VersionDownloadSource::GhFast
|
||||
| VersionDownloadSource::Custom => version_source_url(&candidate, &github_url),
|
||||
};
|
||||
Some((candidate, url))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn release_tag_from_url(url: &reqwest::Url) -> Option<String> {
|
||||
let mut segments = url.path_segments()?;
|
||||
let tag = segments.next_back()?.trim();
|
||||
@@ -833,19 +900,18 @@ pub(crate) fn core_release_asset_name(version: &str, platform: &CorePlatform) ->
|
||||
pub(crate) async fn download_asset(
|
||||
client: &reqwest::Client,
|
||||
asset: &GithubAsset,
|
||||
download_candidates: &[(VersionDownloadCandidate, String)],
|
||||
archive_path: &Path,
|
||||
window: &tauri::Window,
|
||||
state: &CoreDownloadState,
|
||||
token: &CancellationToken,
|
||||
) -> Result<DownloadedArchive, String> {
|
||||
let urls =
|
||||
std::iter::once(&asset.browser_download_url).chain(asset.fallback_download_urls.iter());
|
||||
) -> Result<(DownloadedArchive, VersionDownloadCandidate), String> {
|
||||
let mut failures = Vec::new();
|
||||
for (index, url) in urls.enumerate() {
|
||||
for (index, (candidate, url)) in download_candidates.iter().enumerate() {
|
||||
if index > 0 {
|
||||
state.progress(
|
||||
window,
|
||||
&format!("下载失败,正在切换到 {}", core_download_source_name(url)),
|
||||
&format!("下载失败,正在切换到 {}", candidate.display_name()),
|
||||
0,
|
||||
asset.size,
|
||||
true,
|
||||
@@ -863,7 +929,7 @@ pub(crate) async fn download_asset(
|
||||
)
|
||||
.await;
|
||||
match result {
|
||||
Ok(downloaded) => return Ok(downloaded),
|
||||
Ok(downloaded) => return Ok((downloaded, candidate.clone())),
|
||||
Err(error) if token.is_cancelled() => {
|
||||
let _ = fs::remove_file(archive_path);
|
||||
return Err(error);
|
||||
|
||||
@@ -756,6 +756,58 @@ fn github_proxy_core_release_uses_proxy_then_official_and_gitcode() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn core_download_candidates_follow_the_complete_fallback_chain() {
|
||||
let release = release_from_tag_for_repositories(
|
||||
"v7.2.80",
|
||||
Some("lzt404/CLIProxyAPI"),
|
||||
&VersionDownloadCandidate::builtin(VersionDownloadSource::Github),
|
||||
);
|
||||
let platform = CorePlatform {
|
||||
os: "windows".to_string(),
|
||||
arch: "x86_64".to_string(),
|
||||
asset_os: "windows".to_string(),
|
||||
asset_arch: "amd64".to_string(),
|
||||
archive_kind: "zip".to_string(),
|
||||
};
|
||||
let asset = select_release_asset(&release, &platform).unwrap();
|
||||
let custom_mirrors = vec!["https://mirror.example.com/".to_string()];
|
||||
let candidates = core_download_candidates(
|
||||
&release.tag_name,
|
||||
asset,
|
||||
VersionDownloadCandidate::builtin(VersionDownloadSource::Github),
|
||||
Some("lzt404/CLIProxyAPI"),
|
||||
&custom_mirrors,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
candidates
|
||||
.iter()
|
||||
.map(|(candidate, _)| candidate.key())
|
||||
.collect::<Vec<_>>(),
|
||||
[
|
||||
"github",
|
||||
"gitcode",
|
||||
"gh-proxy",
|
||||
"gh-fast",
|
||||
"custom:https://mirror.example.com/",
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
candidates
|
||||
.iter()
|
||||
.map(|(_, url)| core_download_source_name(url))
|
||||
.collect::<Vec<_>>(),
|
||||
[
|
||||
"GitHub",
|
||||
"GitCode",
|
||||
"gh-proxy.com",
|
||||
"ghfast.top",
|
||||
"mirror.example.com",
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_asset_names_cover_the_six_supported_gui_targets() {
|
||||
let targets = [
|
||||
|
||||
@@ -63,6 +63,7 @@ function downloadSourceLabel(source: VersionDownloadSource, t: ReturnType<typeof
|
||||
|
||||
export type MessageType = 'info' | 'success' | 'error';
|
||||
const APP_RELEASE_URL = 'https://github.com/router-for-me/EasyCLIProxyAPI/releases/latest';
|
||||
export const DEFAULT_VERSION_DOWNLOAD_SOURCE = 'github';
|
||||
|
||||
export function displayAppVersion(version: string) {
|
||||
const resolvedVersion = version.trim();
|
||||
@@ -391,7 +392,33 @@ export function VersionManagementPage() {
|
||||
});
|
||||
|
||||
loadInstallTask();
|
||||
loadVersionSourceSettings();
|
||||
|
||||
const initializeDefaultVersionSource = async () => {
|
||||
setVersionSourceSaving(true);
|
||||
setVersionSourceError('');
|
||||
try {
|
||||
const settings = await invoke<VersionSourceSettings>('set_download_source', {
|
||||
source: DEFAULT_VERSION_DOWNLOAD_SOURCE,
|
||||
});
|
||||
if (disposed) return;
|
||||
setVersionSource(settings);
|
||||
resetLatest();
|
||||
await Promise.all([
|
||||
checkAppUpdate(),
|
||||
checkLatest(true),
|
||||
]);
|
||||
} catch (error) {
|
||||
if (!disposed) {
|
||||
await loadVersionSourceSettings();
|
||||
setVersionSourceError(String(error));
|
||||
}
|
||||
} finally {
|
||||
if (!disposed) {
|
||||
setVersionSourceSaving(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
void initializeDefaultVersionSource();
|
||||
|
||||
void getVersion()
|
||||
.then((version) => {
|
||||
@@ -559,7 +586,7 @@ export function VersionManagementPage() {
|
||||
<label>
|
||||
<span className="sr-only">{t('kernel.versions.downloadSource')}</span>
|
||||
<select
|
||||
value={versionSource?.source ?? 'github'}
|
||||
value={versionSource?.source ?? DEFAULT_VERSION_DOWNLOAD_SOURCE}
|
||||
disabled={
|
||||
!versionSource
|
||||
|| versionSourceSaving
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
import { displayAppVersion } from '../src/pages/VersionManagementPage';
|
||||
import {
|
||||
DEFAULT_VERSION_DOWNLOAD_SOURCE,
|
||||
displayAppVersion,
|
||||
} from '../src/pages/VersionManagementPage';
|
||||
import { coreUpdateAvailable } from '../src/coreUpdate';
|
||||
import { appUpdateIndicatorState } from '../src/appUpdateModel';
|
||||
|
||||
describe('VersionManagement helper functions', () => {
|
||||
it('defaults every version management session to GitHub', () => {
|
||||
expect(DEFAULT_VERSION_DOWNLOAD_SOURCE).toBe('github');
|
||||
});
|
||||
|
||||
it('formats app versions with v prefix properly', () => {
|
||||
expect(displayAppVersion('1.0.0')).toBe('v1.0.0');
|
||||
expect(displayAppVersion('v1.2.3')).toBe('v1.2.3');
|
||||
|
||||
Reference in new issue
Block a user