diff --git a/Cargo.lock b/Cargo.lock index a82f148..d42cc38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,11 +11,14 @@ dependencies = [ "clap", "colored", "crossterm 0.29.0", + "flate2", "flexi_logger", "futures", "futures-util", + "hex", "indicatif", "log", + "md5", "once_cell", "ratatui 0.30.0", "ratatui-kit", @@ -25,8 +28,29 @@ dependencies = [ "rust-i18n", "serde", "serde_json", + "sha2 0.11.0", + "tar", + "thiserror 2.0.18", "tokio", "tokio-stream", + "zip", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aes" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66bd29a732b644c0431c6140f370d097879203d79b80c94a6747ba0872adaef8" +dependencies = [ + "cipher", + "cpubits", + "cpufeatures 0.3.0", ] [[package]] @@ -230,6 +254,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", + "zeroize", +] + [[package]] name = "bstr" version = "1.12.1" @@ -258,6 +292,15 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +[[package]] +name = "bzip2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" +dependencies = [ + "libbz2-rs-sys", +] + [[package]] name = "cassowary" version = "0.3.0" @@ -311,6 +354,16 @@ dependencies = [ "windows-link", ] +[[package]] +name = "cipher" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea" +dependencies = [ + "crypto-common 0.2.1", + "inout", +] + [[package]] name = "clap" version = "4.6.1" @@ -347,6 +400,12 @@ dependencies = [ "cc", ] +[[package]] +name = "cmov" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" + [[package]] name = "colorchoice" version = "1.0.5" @@ -412,6 +471,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + [[package]] name = "convert_case" version = "0.10.0" @@ -447,6 +518,12 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -456,6 +533,24 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -535,6 +630,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +dependencies = [ + "hybrid-array", +] + [[package]] name = "csscolorparser" version = "0.6.2" @@ -545,6 +649,15 @@ dependencies = [ "phf", ] +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + [[package]] name = "darling" version = "0.23.0" @@ -588,6 +701,12 @@ dependencies = [ "version_check 0.1.5", ] +[[package]] +name = "deflate64" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" + [[package]] name = "deltae" version = "0.3.2" @@ -631,8 +750,21 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "crypto-common 0.1.7", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.0", + "const-oid", + "crypto-common 0.2.1", + "ctutils", + "zeroize", ] [[package]] @@ -746,6 +878,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -764,6 +906,17 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + [[package]] name = "flexi_logger" version = "0.31.8" @@ -952,10 +1105,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 6.0.0", "wasip2", "wasip3", + "wasm-bindgen", ] [[package]] @@ -1056,6 +1211,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", +] + [[package]] name = "http" version = "1.4.0" @@ -1095,6 +1259,15 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "hybrid-array" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.9.0" @@ -1345,6 +1518,15 @@ dependencies = [ "rustversion", ] +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "hybrid-array", +] + [[package]] name = "instability" version = "0.3.12" @@ -1503,6 +1685,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +[[package]] +name = "libbz2-rs-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c" + [[package]] name = "libc" version = "0.2.186" @@ -1592,6 +1780,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "lzma-rust2" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47bb1e988e6fb779cf720ad431242d3f03167c1b3f2b1aae7f1a94b2495b36ae" +dependencies = [ + "sha2 0.10.9", +] + [[package]] name = "mac_address" version = "1.1.8" @@ -1602,6 +1799,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "md5" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0" + [[package]] name = "memchr" version = "2.8.0" @@ -1635,6 +1838,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.2.0" @@ -1791,6 +2004,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5a797f0e07bdf071d15742978fc3128ec6c22891c31a3a931513263904c982a" +[[package]] +name = "pbkdf2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" +dependencies = [ + "digest 0.11.3", + "hmac", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -1837,7 +2060,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" dependencies = [ "pest", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -1925,6 +2148,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppmd-rust" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -2278,6 +2507,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-core", + "futures-util", "h2", "http", "http-body", @@ -2297,12 +2527,14 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-rustls", + "tokio-util", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", ] @@ -2714,6 +2946,17 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + [[package]] name = "sha2" version = "0.10.9" @@ -2721,8 +2964,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -2762,6 +3016,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + [[package]] name = "simd_cesu8" version = "1.1.1" @@ -2948,6 +3208,17 @@ dependencies = [ "libc", ] +[[package]] +name = "tar" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "terminfo" version = "0.9.0" @@ -2994,7 +3265,7 @@ dependencies = [ "pest", "pest_derive", "phf", - "sha2", + "sha2 0.10.9", "signal-hook", "siphasher", "terminfo", @@ -3058,6 +3329,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", + "js-sys", "libc", "num-conv", "num_threads", @@ -3324,6 +3596,12 @@ dependencies = [ "unicode-width 0.2.0", ] +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + [[package]] name = "typenum" version = "1.20.0" @@ -3589,6 +3867,19 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasmparser" version = "0.244.0" @@ -3648,7 +3939,7 @@ checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7" dependencies = [ "getrandom 0.3.4", "mac_address", - "sha2", + "sha2 0.10.9", "thiserror 1.0.69", "uuid", ] @@ -4068,6 +4359,16 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix 1.1.4", +] + [[package]] name = "yoke" version = "0.8.2" @@ -4171,8 +4472,81 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "zip" +version = "8.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" +dependencies = [ + "aes", + "bzip2", + "constant_time_eq", + "crc32fast", + "deflate64", + "flate2", + "getrandom 0.4.2", + "hmac", + "indexmap", + "lzma-rust2", + "memchr", + "pbkdf2", + "ppmd-rust", + "sha1", + "time", + "typed-path", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zlib-rs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" + [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index c5591dd..d161431 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" @@ -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" \ No newline at end of file diff --git a/src/core/db/mod.rs b/src/core/db/mod.rs index 3e9645d..98139ab 100644 --- a/src/core/db/mod.rs +++ b/src/core/db/mod.rs @@ -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")?; diff --git a/src/main.rs b/src/main.rs index 6015173..90ce7d0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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"), ) diff --git a/src/platform/detect.rs b/src/platform/detect.rs new file mode 100644 index 0000000..2a35861 --- /dev/null +++ b/src/platform/detect.rs @@ -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 } +} \ No newline at end of file diff --git a/src/platform/mod.rs b/src/platform/mod.rs index f23be0d..717a864 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -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 { +/// 获取用户目录 +pub fn get_platform_user_dir() -> Result { #[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 { + #[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 { - 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 { + 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 { - 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 { + 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 { + Ok( + get_platform_app_dir()? + .join("omega_code.db") + ) } #[macro_export] diff --git a/src/runtime/download.rs b/src/runtime/download.rs new file mode 100644 index 0000000..e7984de --- /dev/null +++ b/src/runtime/download.rs @@ -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( + 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(()) +} \ No newline at end of file diff --git a/src/runtime/extract.rs b/src/runtime/extract.rs new file mode 100644 index 0000000..8748b24 --- /dev/null +++ b/src/runtime/extract.rs @@ -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( + 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( + 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(()) +} \ No newline at end of file diff --git a/src/runtime/manifest.rs b/src/runtime/manifest.rs new file mode 100644 index 0000000..5f8b78b --- /dev/null +++ b/src/runtime/manifest.rs @@ -0,0 +1,100 @@ +use crate::platform::detect::{Arch, Os, Platform}; + +pub struct RuntimePackage { + pub url: &'static str, + pub checksum: &'static str, + pub is_sha256: bool, + pub archive_name: &'static str, +} + +pub fn jre_package(platform: Platform) -> RuntimePackage { + match (platform.os, platform.arch) { + (Os::Windows, Arch::X64) => RuntimePackage { + url: "https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.9%2B10/OpenJDK21U-jre_x64_windows_hotspot_21.0.9_10.zip", + checksum: "39c5e23f3ce4d420663afba8ffde28034b72e2b3e240943dc2321bc1f912eef9", + is_sha256: true, + archive_name: "jre.zip", + }, + + (Os::Windows, Arch::Arm64) => RuntimePackage { + url: "https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.9%2B10/OpenJDK21U-jre_aarch64_windows_hotspot_21.0.9_10.zip", + checksum: "418099dc1e6dfe9b374fedbb33a10786964a1c465e5b09c385ecb1a4e2fe883c", + is_sha256: true, + archive_name: "jre.zip", + }, + + (Os::Linux, Arch::X64) => RuntimePackage { + url: "https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.9%2B10/OpenJDK21U-jre_x64_linux_hotspot_21.0.9_10.tar.gz", + checksum: "aeab55d064a1a27a3744b0880b9b414077b4ed2b1790817eea3df60aec946431", + is_sha256: true, + archive_name: "jre.tar.gz", + }, + + (Os::Linux, Arch::Arm64) => RuntimePackage { + url: "https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.9%2B10/OpenJDK21U-jre_aarch64_linux_hotspot_21.0.9_10.tar.gz", + checksum: "7f8c230ba505b418e4288e2b34758a6e4da32470944740e5ba0cfaae02271c22", + is_sha256: true, + archive_name: "jre.tar.gz", + }, + + (Os::MacOS, Arch::X64) => RuntimePackage { + url: "https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.9%2B10/OpenJDK21U-jre_x64_mac_hotspot_21.0.9_10.tar.gz", + checksum: "945abc49249f1e89a2a6a008d70d63dc42b25bbe5e1711ff97aeec70063008d2", + is_sha256: true, + archive_name: "jre.tar.gz", + }, + + (Os::MacOS, Arch::Arm64) => RuntimePackage { + url: "https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.9%2B10/OpenJDK21U-jre_aarch64_mac_hotspot_21.0.9_10.tar.gz", + checksum: "1f7f6506b598e85d7d8ff8b36563d98657d2d81b16bfca3cd242d7906cfbd11b", + is_sha256: true, + archive_name: "jre.tar.gz", + }, + } +} + +pub fn python_package(platform: Platform) -> RuntimePackage { + match (platform.os, platform.arch) { + (Os::Windows, Arch::X64) => RuntimePackage { + url: "https://www.python.org/ftp/python/3.11.9/python-3.11.9-embed-amd64.zip", + checksum: "6d9aa08531d48fcc261ba667e2df17c4", + is_sha256: false, + archive_name: "python.zip", + }, + + (Os::Windows, Arch::Arm64) => RuntimePackage { + url: "https://www.python.org/ftp/python/3.11.9/python-3.11.9-embed-arm64.zip", + checksum: "8611b6aa35483ab1c61d45e0d9f2de0d", + is_sha256: false, + archive_name: "python.zip", + }, + + (Os::Linux, Arch::X64) => RuntimePackage { + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13+20250626-x86_64-unknown-linux-gnu-install_only.tar.gz", + checksum: "3bf2066dd96c86aacfd2b016699667e2a0bcb97ec63fb7791e230c7deda0a90f", + is_sha256: true, + archive_name: "python.tar.gz", + }, + + (Os::Linux, Arch::Arm64) => RuntimePackage { + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13+20250626-aarch64-unknown-linux-gnu-install_only.tar.gz", + checksum: "7b65d27357f3101576e6a30ebfd1462d794a02be2c2068abfb147480a8a494df", + is_sha256: true, + archive_name: "python.tar.gz", + }, + + (Os::MacOS, Arch::X64) => RuntimePackage { + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13+20250626-x86_64-apple-darwin-install_only.tar.gz", + checksum: "82a709c1d8220f26a0d3b35a566047e1fed0a41d39013953720ce92dac527a3d", + is_sha256: true, + archive_name: "python.tar.gz", + }, + + (Os::MacOS, Arch::Arm64) => RuntimePackage { + url: "https://github.com/astral-sh/python-build-standalone/releases/download/20250626/cpython-3.11.13+20250626-aarch64-apple-darwin-install_only.tar.gz", + checksum: "fd3bc3b011b49fc66ccc85099c7ec646242f5802f7759e5c26f06a9d41476ae2", + is_sha256: true, + archive_name: "python.tar.gz", + }, + } +} \ No newline at end of file diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs index e69de29..1375bed 100644 --- a/src/runtime/mod.rs +++ b/src/runtime/mod.rs @@ -0,0 +1,4 @@ +mod manifest; +mod download; +mod extract; +mod runtime; \ No newline at end of file diff --git a/src/runtime/runtime.rs b/src/runtime/runtime.rs new file mode 100644 index 0000000..0d544df --- /dev/null +++ b/src/runtime/runtime.rs @@ -0,0 +1,302 @@ +use std::{ + fs, + path::{Path, PathBuf}, +}; + +use crate::{ + platform::{ + detect::{current_platform, Os}, + get_app_runtime_dir, + }, + runtime::{ + + download::*, + extract::*, + manifest::*, + }, +}; + +pub struct RuntimePaths { + pub java: PathBuf, + pub python: PathBuf, +} + +pub async fn ensure_jre< + DF, + EF, +>( + mut download_callback: DF, + mut extract_callback: EF, +) -> anyhow::Result +where + DF: FnMut(DownloadProgress), + EF: FnMut(ExtractProgress), +{ + let platform = current_platform(); + + let runtime_root = + get_app_runtime_dir()?; + + let current_dir = + runtime_root + .join("jre") + .join("current"); + + if let Ok(path) = + find_java_binary( + ¤t_dir, + platform.os, + ) + { + return Ok(path); + } + + let package = + jre_package(platform); + + let download_dir = + runtime_root.join("downloads"); + + let tmp_dir = + runtime_root.join("tmp"); + + fs::create_dir_all(&download_dir)?; + fs::create_dir_all(&tmp_dir)?; + + let archive_path = + download_dir.join("jre.download"); + + let install_tmp = + tmp_dir.join("jre.tmp"); + + if install_tmp.exists() { + fs::remove_dir_all(&install_tmp)?; + } + + download_file( + package.url, + &archive_path, + &mut download_callback, + ) + .await?; + + if package.is_sha256 { + verify_sha256( + &archive_path, + package.checksum, + )?; + } else { + verify_md5( + &archive_path, + package.checksum, + )?; + } + + fs::create_dir_all(&install_tmp)?; + + if package.archive_name.ends_with(".zip") { + extract_zip( + &archive_path, + &install_tmp, + &mut extract_callback, + )?; + } else { + extract_tar_gz( + &archive_path, + &install_tmp, + &mut extract_callback, + )?; + } + + if current_dir.exists() { + fs::remove_dir_all(¤t_dir)?; + } + + if let Some(parent) = current_dir.parent() { + fs::create_dir_all(parent)?; + } + + fs::rename( + &install_tmp, + ¤t_dir, + )?; + + find_java_binary( + ¤t_dir, + platform.os, + ) +} + +pub async fn ensure_python< + DF, + EF, +>( + mut download_callback: DF, + mut extract_callback: EF, +) -> anyhow::Result +where + DF: FnMut(DownloadProgress), + EF: FnMut(ExtractProgress), +{ + let platform = current_platform(); + + let runtime_root = + get_app_runtime_dir()?; + + let current_dir = + runtime_root + .join("python") + .join("current"); + + if let Ok(path) = + find_python_binary( + ¤t_dir, + platform.os, + ) + { + return Ok(path); + } + + let package = + python_package(platform); + + let download_dir = + runtime_root.join("downloads"); + + let tmp_dir = + runtime_root.join("tmp"); + + fs::create_dir_all(&download_dir)?; + fs::create_dir_all(&tmp_dir)?; + + let archive_path = + download_dir.join("python.download"); + + let install_tmp = + tmp_dir.join("python.tmp"); + + if install_tmp.exists() { + fs::remove_dir_all(&install_tmp)?; + } + + download_file( + package.url, + &archive_path, + &mut download_callback, + ) + .await?; + + if package.is_sha256 { + verify_sha256( + &archive_path, + package.checksum, + )?; + } else { + verify_md5( + &archive_path, + package.checksum, + )?; + } + + fs::create_dir_all(&install_tmp)?; + + if package.archive_name.ends_with(".zip") { + extract_zip( + &archive_path, + &install_tmp, + &mut extract_callback, + )?; + } else { + extract_tar_gz( + &archive_path, + &install_tmp, + &mut extract_callback, + )?; + } + + if current_dir.exists() { + fs::remove_dir_all(¤t_dir)?; + } + + if let Some(parent) = current_dir.parent() { + fs::create_dir_all(parent)?; + } + + fs::rename( + &install_tmp, + ¤t_dir, + )?; + + find_python_binary( + ¤t_dir, + platform.os, + ) +} + +fn find_java_binary( + dir: &Path, + os: Os, +) -> anyhow::Result { + let names = match os { + Os::Windows => vec!["java.exe"], + _ => vec!["java"], + }; + + find_binary_recursive(dir, &names) +} + +fn find_python_binary( + dir: &Path, + os: Os, +) -> anyhow::Result { + let names = match os { + Os::Windows => vec!["python.exe"], + _ => vec!["python3", "python"], + }; + + find_binary_recursive(dir, &names) +} + +fn find_binary_recursive( + dir: &Path, + names: &[&str], +) -> anyhow::Result { + if !dir.exists() { + anyhow::bail!("runtime not installed"); + } + + for entry in walk(dir)? { + if let Some(name) = + entry.file_name() + { + let name = + name.to_string_lossy(); + + if names.iter().any(|n| *n == name) { + return Ok(entry); + } + } + } + + anyhow::bail!("binary not found") +} + +fn walk( + dir: &Path, +) -> anyhow::Result> { + let mut result = vec![]; + + for entry in fs::read_dir(dir)? { + let entry = entry?; + + let path = entry.path(); + + if path.is_dir() { + result.extend(walk(&path)?); + } else { + result.push(path); + } + } + + Ok(result) +} \ No newline at end of file