Skip to content

Commit b54379c

Browse files
authored
feat(runtime): 内置运行时完善 (#19)
1 parent 213b37b commit b54379c

11 files changed

Lines changed: 1090 additions & 31 deletions

File tree

Cargo.lock

Lines changed: 381 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ anyhow = "1.0.102"
1616
tokio = { version = "1.52.3", features = ["full"] }
1717
colored = "3.1.1"
1818
indicatif = "0.18.4"
19-
reqwest = "0.13.3"
19+
reqwest = { version = "0.13.3", features = ["stream"] }
2020
tokio-stream = "0.1.18"
2121
futures = "0.3.32"
2222
futures-util = "0.3.32"
@@ -32,3 +32,10 @@ chrono = "0.4.44"
3232
rust-i18n = "4.0.0"
3333
once_cell = "1.21.4"
3434
ratatui-kit = { version = "0.5.9", features = ["full"] }
35+
sha2 = "0.11.0"
36+
md5 = "0.8.0"
37+
zip = "8.6.0"
38+
flate2 = "1.1.9"
39+
tar = "0.4.45"
40+
thiserror = "2.0.18"
41+
hex = "0.4.3"

src/core/db/mod.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,20 @@ impl DatabaseManager {
4747
let db_path = get_database_path()?;
4848

4949
let conn = Connection::open(&db_path)
50-
.with_context(|| format!("Failed to open database: {}", db_path))?;
50+
.with_context(|| {
51+
format!(
52+
"Failed to open database: {}",
53+
db_path.display()
54+
)
55+
})?;
5156

5257
info!(
53-
"{}",
54-
t!("database_path", database_path = &db_path)
55-
);
58+
"{}",
59+
t!(
60+
"database_path",
61+
database_path = db_path.display().to_string()
62+
)
63+
);
5664

5765
db_init::init_all_tables(&conn)
5866
.context("Failed to initialize database")?;

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ fn init_logger() {
2828
.unwrap()
2929
.log_to_file(
3030
FileSpec::default()
31-
.directory(app_dir!() + "/logs")
31+
.directory(app_dir!().display().to_string() + "/logs")
3232
.basename("omega")
3333
.suffix("log"),
3434
)

src/platform/detect.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#[derive(Debug, Clone, Copy)]
2+
pub enum Os {
3+
Windows,
4+
Linux,
5+
MacOS,
6+
}
7+
8+
#[derive(Debug, Clone, Copy)]
9+
pub enum Arch {
10+
X64,
11+
Arm64,
12+
}
13+
14+
#[derive(Debug, Clone, Copy)]
15+
pub struct Platform {
16+
pub os: Os,
17+
pub arch: Arch,
18+
}
19+
20+
pub fn current_platform() -> Platform {
21+
let os = match std::env::consts::OS {
22+
"windows" => Os::Windows,
23+
"linux" => Os::Linux,
24+
"macos" => Os::MacOS,
25+
_ => panic!("unsupported os"),
26+
};
27+
28+
let arch = match std::env::consts::ARCH {
29+
"x86_64" => Arch::X64,
30+
"aarch64" => Arch::Arm64,
31+
_ => panic!("unsupported arch"),
32+
};
33+
34+
Platform { os, arch }
35+
}

src/platform/mod.rs

Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,81 @@
1-
use anyhow::Result;
1+
pub mod detect;
2+
use anyhow::{anyhow, Result};
3+
use std::path::PathBuf;
24

3-
pub fn get_platform_user_dir() -> Result<String> {
5+
/// 获取用户目录
6+
pub fn get_platform_user_dir() -> Result<PathBuf> {
47
#[cfg(target_os = "windows")]
58
let user_dir = std::env::var("USERPROFILE")
6-
.map_err(|e| anyhow::anyhow!("Failed to get USERPROFILE environment variable: {}", e))?;
9+
.map_err(|e| anyhow!("Failed to get USERPROFILE: {}", e))?;
710

8-
#[cfg(target_os = "linux")]
11+
#[cfg(any(target_os = "linux", target_os = "macos"))]
912
let user_dir = std::env::var("HOME")
10-
.map_err(|e| anyhow::anyhow!("Failed to get HOME environment variable: {}", e))?;
13+
.map_err(|e| anyhow!("Failed to get HOME: {}", e))?;
1114

12-
#[cfg(target_os = "macos")]
13-
let user_dir = std::env::var("HOME")
14-
.map_err(|e| anyhow::anyhow!("Failed to get HOME environment variable: {}", e))?;
15+
Ok(PathBuf::from(user_dir))
16+
}
17+
18+
/// 获取系统 temp 目录
19+
pub fn get_os_temp_dir() -> Result<PathBuf> {
20+
#[cfg(target_os = "windows")]
21+
{
22+
let temp_dir = std::env::var("TEMP")
23+
.map_err(|e| anyhow!("Failed to get TEMP: {}", e))?;
24+
25+
Ok(PathBuf::from(temp_dir))
26+
}
27+
28+
#[cfg(any(target_os = "linux", target_os = "macos"))]
29+
{
30+
Ok(PathBuf::from("/tmp"))
31+
}
1532

16-
Ok(user_dir)
33+
#[cfg(not(any(
34+
target_os = "windows",
35+
target_os = "linux",
36+
target_os = "macos"
37+
)))]
38+
{
39+
Err(anyhow!("Unsupported operating system"))
40+
}
1741
}
1842

19-
pub fn get_platform_app_dir() -> Result<String> {
20-
let user_dir = get_platform_user_dir()?;
21-
let app_dir = format!("{}/.omega", user_dir);
22-
std::fs::create_dir_all(&app_dir)
23-
.map_err(|e| anyhow::anyhow!("Failed to create app directory '{}': {}", app_dir, e))?;
43+
/// 获取应用目录
44+
pub fn get_platform_app_dir() -> Result<PathBuf> {
45+
let app_dir = get_platform_user_dir()?.join(".omega");
46+
47+
std::fs::create_dir_all(&app_dir).map_err(|e| {
48+
anyhow!(
49+
"Failed to create app directory '{}': {}",
50+
app_dir.display(),
51+
e
52+
)
53+
})?;
54+
2455
Ok(app_dir)
2556
}
2657

27-
pub fn get_database_path() -> Result<String> {
28-
let app_dir = get_platform_app_dir()?;
29-
let db_path = format!("{}/omega_code.db", app_dir);
30-
Ok(db_path)
58+
/// 获取 runtime 目录
59+
pub fn get_app_runtime_dir() -> Result<PathBuf> {
60+
let runtime_dir = get_platform_app_dir()?.join("runtime");
61+
62+
std::fs::create_dir_all(&runtime_dir).map_err(|e| {
63+
anyhow!(
64+
"Failed to create runtime directory '{}': {}",
65+
runtime_dir.display(),
66+
e
67+
)
68+
})?;
69+
70+
Ok(runtime_dir)
71+
}
72+
73+
/// 获取数据库路径
74+
pub fn get_database_path() -> Result<PathBuf> {
75+
Ok(
76+
get_platform_app_dir()?
77+
.join("omega_code.db")
78+
)
3179
}
3280

3381
#[macro_export]

src/runtime/download.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
use futures_util::StreamExt;
2+
use sha2::{Digest, Sha256};
3+
4+
use std::{
5+
fs::File,
6+
io::Write,
7+
path::Path,
8+
time::Instant,
9+
};
10+
11+
pub struct DownloadProgress {
12+
pub downloaded: u64,
13+
pub total: u64,
14+
pub speed: f64,
15+
pub percent: f64,
16+
}
17+
18+
pub async fn download_file<F>(
19+
url: &str,
20+
save_path: &Path,
21+
mut callback: F,
22+
) -> anyhow::Result<()>
23+
where
24+
F: FnMut(DownloadProgress),
25+
{
26+
let response = reqwest::get(url).await?;
27+
28+
let total = response.content_length().unwrap_or(0);
29+
30+
let mut stream = response.bytes_stream();
31+
32+
let mut file = File::create(save_path)?;
33+
34+
let mut downloaded = 0u64;
35+
36+
let start = Instant::now();
37+
38+
while let Some(chunk) = stream.next().await {
39+
let chunk = chunk?;
40+
41+
file.write_all(&chunk)?;
42+
43+
downloaded += chunk.len() as u64;
44+
45+
let elapsed = start.elapsed().as_secs_f64();
46+
47+
callback(DownloadProgress {
48+
downloaded,
49+
total,
50+
speed: downloaded as f64 / elapsed,
51+
percent: if total == 0 {
52+
0.0
53+
} else {
54+
downloaded as f64 / total as f64 * 100.0
55+
},
56+
});
57+
}
58+
59+
Ok(())
60+
}
61+
62+
pub fn verify_sha256(
63+
path: &Path,
64+
expected: &str,
65+
) -> anyhow::Result<()> {
66+
let data = std::fs::read(path)?;
67+
68+
let mut hasher = Sha256::new();
69+
70+
hasher.update(data);
71+
72+
let result = hex::encode(hasher.finalize());
73+
74+
if result != expected {
75+
anyhow::bail!("sha256 mismatch");
76+
}
77+
78+
Ok(())
79+
}
80+
81+
pub fn verify_md5(
82+
path: &Path,
83+
expected: &str,
84+
) -> anyhow::Result<()> {
85+
let data = std::fs::read(path)?;
86+
87+
let result = format!("{:x}", md5::compute(data));
88+
89+
if result != expected {
90+
anyhow::bail!("md5 mismatch");
91+
}
92+
93+
Ok(())
94+
}

src/runtime/extract.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use flate2::read::GzDecoder;
2+
3+
use std::{
4+
fs::File,
5+
io,
6+
path::Path,
7+
};
8+
9+
use tar::Archive;
10+
use zip::ZipArchive;
11+
12+
pub struct ExtractProgress {
13+
pub current: u64,
14+
pub total: u64,
15+
}
16+
17+
pub fn extract_zip<F>(
18+
archive_path: &Path,
19+
target_dir: &Path,
20+
mut callback: F,
21+
) -> anyhow::Result<()>
22+
where
23+
F: FnMut(ExtractProgress),
24+
{
25+
let file = File::open(archive_path)?;
26+
27+
let mut archive = ZipArchive::new(file)?;
28+
29+
let total = archive.len() as u64;
30+
31+
for i in 0..archive.len() {
32+
let mut entry = archive.by_index(i)?;
33+
34+
let outpath = target_dir.join(entry.name());
35+
36+
if entry.name().ends_with('/') {
37+
std::fs::create_dir_all(&outpath)?;
38+
} else {
39+
if let Some(parent) = outpath.parent() {
40+
std::fs::create_dir_all(parent)?;
41+
}
42+
43+
let mut outfile = File::create(&outpath)?;
44+
45+
io::copy(&mut entry, &mut outfile)?;
46+
}
47+
48+
callback(ExtractProgress {
49+
current: i as u64 + 1,
50+
total,
51+
});
52+
}
53+
54+
Ok(())
55+
}
56+
57+
pub fn extract_tar_gz<F>(
58+
archive_path: &Path,
59+
target_dir: &Path,
60+
mut callback: F,
61+
) -> anyhow::Result<()>
62+
where
63+
F: FnMut(ExtractProgress),
64+
{
65+
let file = File::open(archive_path)?;
66+
67+
let decoder = GzDecoder::new(file);
68+
69+
let mut archive = Archive::new(decoder);
70+
71+
let mut index = 0;
72+
73+
for entry in archive.entries()? {
74+
let mut entry = entry?;
75+
76+
entry.unpack_in(target_dir)?;
77+
78+
index += 1;
79+
80+
callback(ExtractProgress {
81+
current: index,
82+
total: 0,
83+
});
84+
}
85+
86+
Ok(())
87+
}

0 commit comments

Comments
 (0)