Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
388 changes: 381 additions & 7 deletions Cargo.lock

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ anyhow = "1.0.102"
tokio = { version = "1.52.3", features = ["full"] }
colored = "3.1.1"
indicatif = "0.18.4"
reqwest = "0.13.3"
reqwest = { version = "0.13.3", features = ["stream"] }
tokio-stream = "0.1.18"
futures = "0.3.32"
futures-util = "0.3.32"
Expand All @@ -32,3 +32,10 @@ chrono = "0.4.44"
rust-i18n = "4.0.0"
once_cell = "1.21.4"
ratatui-kit = { version = "0.5.9", features = ["full"] }
sha2 = "0.11.0"
md5 = "0.8.0"
zip = "8.6.0"
flate2 = "1.1.9"
tar = "0.4.45"
thiserror = "2.0.18"
hex = "0.4.3"
16 changes: 12 additions & 4 deletions src/core/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,20 @@ impl DatabaseManager {
let db_path = get_database_path()?;

let conn = Connection::open(&db_path)
.with_context(|| format!("Failed to open database: {}", db_path))?;
.with_context(|| {
format!(
"Failed to open database: {}",
db_path.display()
)
})?;

info!(
"{}",
t!("database_path", database_path = &db_path)
);
"{}",
t!(
"database_path",
database_path = db_path.display().to_string()
)
);

db_init::init_all_tables(&conn)
.context("Failed to initialize database")?;
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ fn init_logger() {
.unwrap()
.log_to_file(
FileSpec::default()
.directory(app_dir!() + "/logs")
.directory(app_dir!().display().to_string() + "/logs")
.basename("omega")
.suffix("log"),
)
Expand Down
35 changes: 35 additions & 0 deletions src/platform/detect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#[derive(Debug, Clone, Copy)]
pub enum Os {
Windows,
Linux,
MacOS,
}

#[derive(Debug, Clone, Copy)]
pub enum Arch {
X64,
Arm64,
}

#[derive(Debug, Clone, Copy)]
pub struct Platform {
pub os: Os,
pub arch: Arch,
}

pub fn current_platform() -> Platform {
let os = match std::env::consts::OS {
"windows" => Os::Windows,
"linux" => Os::Linux,
"macos" => Os::MacOS,
_ => panic!("unsupported os"),
};

let arch = match std::env::consts::ARCH {
"x86_64" => Arch::X64,
"aarch64" => Arch::Arm64,
_ => panic!("unsupported arch"),
};

Platform { os, arch }
}
84 changes: 66 additions & 18 deletions src/platform/mod.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,81 @@
use anyhow::Result;
pub mod detect;
use anyhow::{anyhow, Result};
use std::path::PathBuf;

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

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

#[cfg(target_os = "macos")]
let user_dir = std::env::var("HOME")
.map_err(|e| anyhow::anyhow!("Failed to get HOME environment variable: {}", e))?;
Ok(PathBuf::from(user_dir))
}

/// 获取系统 temp 目录
pub fn get_os_temp_dir() -> Result<PathBuf> {
#[cfg(target_os = "windows")]
{
let temp_dir = std::env::var("TEMP")
.map_err(|e| anyhow!("Failed to get TEMP: {}", e))?;

Ok(PathBuf::from(temp_dir))
}

#[cfg(any(target_os = "linux", target_os = "macos"))]
{
Ok(PathBuf::from("/tmp"))
}

Ok(user_dir)
#[cfg(not(any(
target_os = "windows",
target_os = "linux",
target_os = "macos"
)))]
{
Err(anyhow!("Unsupported operating system"))
}
}

pub fn get_platform_app_dir() -> Result<String> {
let user_dir = get_platform_user_dir()?;
let app_dir = format!("{}/.omega", user_dir);
std::fs::create_dir_all(&app_dir)
.map_err(|e| anyhow::anyhow!("Failed to create app directory '{}': {}", app_dir, e))?;
/// 获取应用目录
pub fn get_platform_app_dir() -> Result<PathBuf> {
let app_dir = get_platform_user_dir()?.join(".omega");

std::fs::create_dir_all(&app_dir).map_err(|e| {
anyhow!(
"Failed to create app directory '{}': {}",
app_dir.display(),
e
)
})?;

Ok(app_dir)
}

pub fn get_database_path() -> Result<String> {
let app_dir = get_platform_app_dir()?;
let db_path = format!("{}/omega_code.db", app_dir);
Ok(db_path)
/// 获取 runtime 目录
pub fn get_app_runtime_dir() -> Result<PathBuf> {
let runtime_dir = get_platform_app_dir()?.join("runtime");

std::fs::create_dir_all(&runtime_dir).map_err(|e| {
anyhow!(
"Failed to create runtime directory '{}': {}",
runtime_dir.display(),
e
)
})?;

Ok(runtime_dir)
}

/// 获取数据库路径
pub fn get_database_path() -> Result<PathBuf> {
Ok(
get_platform_app_dir()?
.join("omega_code.db")
)
}

#[macro_export]
Expand Down
94 changes: 94 additions & 0 deletions src/runtime/download.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use futures_util::StreamExt;
use sha2::{Digest, Sha256};

use std::{
fs::File,
io::Write,
path::Path,
time::Instant,
};

pub struct DownloadProgress {
pub downloaded: u64,
pub total: u64,
pub speed: f64,
pub percent: f64,
}

pub async fn download_file<F>(
url: &str,
save_path: &Path,
mut callback: F,
) -> anyhow::Result<()>
where
F: FnMut(DownloadProgress),
{
let response = reqwest::get(url).await?;

let total = response.content_length().unwrap_or(0);

let mut stream = response.bytes_stream();

let mut file = File::create(save_path)?;

let mut downloaded = 0u64;

let start = Instant::now();

while let Some(chunk) = stream.next().await {
let chunk = chunk?;

file.write_all(&chunk)?;

downloaded += chunk.len() as u64;

let elapsed = start.elapsed().as_secs_f64();

callback(DownloadProgress {
downloaded,
total,
speed: downloaded as f64 / elapsed,
percent: if total == 0 {
0.0
} else {
downloaded as f64 / total as f64 * 100.0
},
});
}

Ok(())
}

pub fn verify_sha256(
path: &Path,
expected: &str,
) -> anyhow::Result<()> {
let data = std::fs::read(path)?;

let mut hasher = Sha256::new();

hasher.update(data);

let result = hex::encode(hasher.finalize());

if result != expected {
anyhow::bail!("sha256 mismatch");
}

Ok(())
}

pub fn verify_md5(
path: &Path,
expected: &str,
) -> anyhow::Result<()> {
let data = std::fs::read(path)?;

let result = format!("{:x}", md5::compute(data));

if result != expected {
anyhow::bail!("md5 mismatch");
}

Ok(())
}
87 changes: 87 additions & 0 deletions src/runtime/extract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use flate2::read::GzDecoder;

use std::{
fs::File,
io,
path::Path,
};

use tar::Archive;
use zip::ZipArchive;

pub struct ExtractProgress {
pub current: u64,
pub total: u64,
}

pub fn extract_zip<F>(
archive_path: &Path,
target_dir: &Path,
mut callback: F,
) -> anyhow::Result<()>
where
F: FnMut(ExtractProgress),
{
let file = File::open(archive_path)?;

let mut archive = ZipArchive::new(file)?;

let total = archive.len() as u64;

for i in 0..archive.len() {
let mut entry = archive.by_index(i)?;

let outpath = target_dir.join(entry.name());

if entry.name().ends_with('/') {
std::fs::create_dir_all(&outpath)?;
} else {
if let Some(parent) = outpath.parent() {
std::fs::create_dir_all(parent)?;
}

let mut outfile = File::create(&outpath)?;

io::copy(&mut entry, &mut outfile)?;
}

callback(ExtractProgress {
current: i as u64 + 1,
total,
});
}

Ok(())
}

pub fn extract_tar_gz<F>(
archive_path: &Path,
target_dir: &Path,
mut callback: F,
) -> anyhow::Result<()>
where
F: FnMut(ExtractProgress),
{
let file = File::open(archive_path)?;

let decoder = GzDecoder::new(file);

let mut archive = Archive::new(decoder);

let mut index = 0;

for entry in archive.entries()? {
let mut entry = entry?;

entry.unpack_in(target_dir)?;

index += 1;

callback(ExtractProgress {
current: index,
total: 0,
});
}

Ok(())
}
Loading
Loading