feat:移除了弹窗,服务器添加sls
This commit is contained in:
327
packages/app-lib/src/api/terracotta/binary.rs
Normal file
327
packages/app-lib/src/api/terracotta/binary.rs
Normal file
@ -0,0 +1,327 @@
|
||||
use eyre::{Context, bail};
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tracing::info;
|
||||
|
||||
pub fn terracotta_platform_key() -> &'static str {
|
||||
match (std::env::consts::OS, std::env::consts::ARCH) {
|
||||
("linux", "x86_64") => "linux-x86_64",
|
||||
("linux", "aarch64") => "linux-arm64",
|
||||
("linux", "riscv64") => "linux-riscv64",
|
||||
("linux", "loongarch64") => "linux-loongarch64",
|
||||
("macos", "x86_64") => "macos-x86_64",
|
||||
("macos", "aarch64") => "macos-arm64",
|
||||
("windows", "x86_64") => "windows-x86_64",
|
||||
("windows", "aarch64") => "windows-arm64",
|
||||
("freebsd", "x86_64") => "freebsd-x86_64",
|
||||
_ => "unsupported",
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn terracotta_binary_name() -> &'static str {
|
||||
if cfg!(target_os = "windows") {
|
||||
"terracotta.exe"
|
||||
} else {
|
||||
"terracotta"
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn versioned_terracotta_binary_name(
|
||||
version: &str,
|
||||
platform: &str,
|
||||
) -> String {
|
||||
let extension = if cfg!(target_os = "windows") {
|
||||
".exe"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
format!("terracotta-{version}-{platform}{extension}")
|
||||
}
|
||||
|
||||
pub(super) fn validate_terracotta_version(version: &str) -> eyre::Result<()> {
|
||||
if version.is_empty()
|
||||
|| version.len() > 64
|
||||
|| version.contains("..")
|
||||
|| !version.chars().all(|character| {
|
||||
character.is_ascii_alphanumeric()
|
||||
|| matches!(character, '.' | '-' | '_')
|
||||
})
|
||||
{
|
||||
bail!("invalid terracotta version: {version}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn is_terracotta_executable(path: &Path) -> bool {
|
||||
let Ok(metadata) = std::fs::symlink_metadata(path) else {
|
||||
return false;
|
||||
};
|
||||
if !metadata.file_type().is_file() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut magic = [0_u8; 4];
|
||||
if std::fs::File::open(path)
|
||||
.and_then(|mut file| file.read_exact(&mut magic))
|
||||
.is_err()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
matches!(
|
||||
magic,
|
||||
[0xce, 0xfa, 0xed, 0xfe]
|
||||
| [0xcf, 0xfa, 0xed, 0xfe]
|
||||
| [0xfe, 0xed, 0xfa, 0xce]
|
||||
| [0xfe, 0xed, 0xfa, 0xcf]
|
||||
| [0xca, 0xfe, 0xba, 0xbe]
|
||||
| [0xbe, 0xba, 0xfe, 0xca]
|
||||
| [0xca, 0xfe, 0xba, 0xbf]
|
||||
| [0xbf, 0xba, 0xfe, 0xca]
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
{
|
||||
magic == [0x7f, b'E', b'L', b'F']
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
magic[0..2] == [b'M', b'Z']
|
||||
}
|
||||
|
||||
#[cfg(not(any(
|
||||
target_os = "macos",
|
||||
target_os = "linux",
|
||||
target_os = "freebsd",
|
||||
target_os = "windows"
|
||||
)))]
|
||||
{
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn find_terracotta_executable(
|
||||
dir: &Path,
|
||||
preferred_name: Option<&str>,
|
||||
) -> Option<PathBuf> {
|
||||
let mut pending = vec![dir.to_path_buf()];
|
||||
let mut candidates = Vec::new();
|
||||
|
||||
while let Some(current) = pending.pop() {
|
||||
let entries = match std::fs::read_dir(current) {
|
||||
Ok(entries) => entries,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
let file_type = match entry.file_type() {
|
||||
Ok(file_type) => file_type,
|
||||
Err(_) => continue,
|
||||
};
|
||||
if file_type.is_dir() {
|
||||
pending.push(path);
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(name) = path.file_name().and_then(|name| name.to_str())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
if (name == terracotta_binary_name()
|
||||
|| name.starts_with("terracotta-"))
|
||||
&& is_terracotta_executable(&path)
|
||||
{
|
||||
candidates.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
candidates.sort();
|
||||
preferred_name
|
||||
.and_then(|preferred| {
|
||||
candidates
|
||||
.iter()
|
||||
.find(|path| {
|
||||
path.file_name().and_then(|name| name.to_str())
|
||||
== Some(preferred)
|
||||
})
|
||||
.cloned()
|
||||
})
|
||||
.or_else(|| candidates.into_iter().next())
|
||||
}
|
||||
|
||||
pub(super) fn terracotta_binary_path() -> PathBuf {
|
||||
let base_dir = crate::state::DirectoryInfo::global_handle_if_ready()
|
||||
.map(|directories| directories.config_dir.clone())
|
||||
.or_else(|| {
|
||||
crate::state::DirectoryInfo::initial_settings_dir_path(
|
||||
crate::brand::BUNDLE_IDENTIFIER,
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| PathBuf::from("."));
|
||||
|
||||
terracotta_binary_path_in(&base_dir)
|
||||
}
|
||||
|
||||
pub(super) fn terracotta_binary_path_in(base_dir: &Path) -> PathBuf {
|
||||
base_dir.join("terracotta").join(terracotta_binary_name())
|
||||
}
|
||||
|
||||
fn legacy_terracotta_binary_path() -> PathBuf {
|
||||
std::env::current_exe()
|
||||
.ok()
|
||||
.and_then(|path| path.parent().map(Path::to_path_buf))
|
||||
.unwrap_or_else(|| PathBuf::from("."))
|
||||
.join("terracotta")
|
||||
.join(terracotta_binary_name())
|
||||
}
|
||||
|
||||
pub fn terracotta_download_urls(version: &str, platform: &str) -> Vec<String> {
|
||||
let artifact = format!("terracotta-{version}-{platform}-pkg.tar.gz");
|
||||
vec![
|
||||
format!(
|
||||
"https://gitee.com/burningtnt/Terracotta/releases/download/v{version}/{artifact}"
|
||||
),
|
||||
format!(
|
||||
"https://github.com/burningtnt/Terracotta/releases/download/v{version}/{artifact}"
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
pub(super) fn resolve_terracotta_binary_path(bin_path: &Path) -> PathBuf {
|
||||
let resolved_path = if bin_path.is_absolute() {
|
||||
bin_path.to_path_buf()
|
||||
} else {
|
||||
std::env::current_dir()
|
||||
.unwrap_or_else(|_| PathBuf::from("."))
|
||||
.join(bin_path)
|
||||
};
|
||||
|
||||
let preferred_path = if resolved_path.is_dir() {
|
||||
resolved_path.join(terracotta_binary_name())
|
||||
} else if resolved_path.is_file() {
|
||||
resolved_path
|
||||
} else {
|
||||
resolved_path.with_file_name(terracotta_binary_name())
|
||||
};
|
||||
|
||||
if is_terracotta_executable(&preferred_path) {
|
||||
return preferred_path;
|
||||
}
|
||||
|
||||
preferred_path
|
||||
.parent()
|
||||
.and_then(|parent| find_terracotta_executable(parent, None))
|
||||
.unwrap_or(preferred_path)
|
||||
}
|
||||
|
||||
pub(super) fn resolve_installed_terracotta_binary_path() -> PathBuf {
|
||||
resolve_installed_terracotta_binary_path_from(
|
||||
&terracotta_binary_path(),
|
||||
&legacy_terracotta_binary_path(),
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn resolve_installed_terracotta_binary_path_from(
|
||||
installed_path: &Path,
|
||||
legacy_path: &Path,
|
||||
) -> PathBuf {
|
||||
let installed_path = resolve_terracotta_binary_path(installed_path);
|
||||
if is_terracotta_executable(&installed_path) {
|
||||
return installed_path;
|
||||
}
|
||||
|
||||
let legacy_path = resolve_terracotta_binary_path(legacy_path);
|
||||
if is_terracotta_executable(&legacy_path) {
|
||||
legacy_path
|
||||
} else {
|
||||
installed_path
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn install_terracotta_binary(
|
||||
candidate: &Path,
|
||||
destination: &Path,
|
||||
) -> eyre::Result<()> {
|
||||
let file_name = destination
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.ok_or_else(|| eyre::eyre!("invalid terracotta destination path"))?;
|
||||
let backup = destination.with_file_name(format!("{file_name}.old"));
|
||||
|
||||
if backup.exists() {
|
||||
tokio::fs::remove_file(&backup)
|
||||
.await
|
||||
.wrap_err("failed to remove stale terracotta backup")?;
|
||||
}
|
||||
if destination.exists() {
|
||||
tokio::fs::rename(destination, &backup)
|
||||
.await
|
||||
.wrap_err("failed to back up the existing terracotta binary")?;
|
||||
}
|
||||
|
||||
if let Err(error) = tokio::fs::rename(candidate, destination).await {
|
||||
if backup.exists() {
|
||||
let _ = tokio::fs::rename(&backup, destination).await;
|
||||
}
|
||||
return Err(error).wrap_err("failed to install terracotta binary");
|
||||
}
|
||||
|
||||
if backup.exists() {
|
||||
tokio::fs::remove_file(&backup).await.wrap_err(
|
||||
"failed to remove terracotta backup after installation",
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) async fn cleanup_legacy_versions(
|
||||
new_version: &str,
|
||||
) -> eyre::Result<()> {
|
||||
let target_dir = terracotta_binary_path()
|
||||
.parent()
|
||||
.map(Path::to_path_buf)
|
||||
.unwrap_or_else(|| PathBuf::from("terracotta"));
|
||||
|
||||
if !target_dir.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut entries = tokio::fs::read_dir(&target_dir).await?;
|
||||
while let Some(entry) = entries.next_entry().await? {
|
||||
let file_type = entry.file_type().await?;
|
||||
let name = entry.file_name();
|
||||
let name = name.to_string_lossy();
|
||||
|
||||
if file_type.is_file()
|
||||
&& (should_cleanup_terracotta_file(&name)
|
||||
|| name.ends_with(".tar.gz")
|
||||
|| name.ends_with(".old"))
|
||||
{
|
||||
tokio::fs::remove_file(entry.path()).await?;
|
||||
info!(
|
||||
"removed extracted terracotta artifact after installing {new_version}: {name}"
|
||||
);
|
||||
}
|
||||
|
||||
if file_type.is_dir() && name.starts_with("terracotta-") {
|
||||
tokio::fs::remove_dir_all(entry.path()).await?;
|
||||
info!(
|
||||
"removed extracted terracotta directory after installing {new_version}: {name}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn should_cleanup_terracotta_file(name: &str) -> bool {
|
||||
name.starts_with("terracotta-")
|
||||
&& name != "terracotta-version.json"
|
||||
&& name != "terracotta-version.json.tmp"
|
||||
}
|
||||
Reference in New Issue
Block a user