From cb1c8d54de3137e73bed8f3e4237535aecd8f1cf Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Thu, 1 May 2025 13:29:26 +0200 Subject: [PATCH] feat: desktop crate is ready to be fixed --- .clippy.toml | 1 + .rustfmt.toml | 9 + Cargo.lock | 2278 ++++++++++++----- Cargo.toml | 143 +- apps/desktop/Cargo.toml | 57 +- apps/desktop/capabilities/default.json | 1 - apps/desktop/tauri.conf.json | 5 +- eslint.config.ts | 6 + packages/core/Cargo.toml | 9 +- packages/core/examples/dirs.rs | 3 +- packages/core/package.json | 3 +- packages/core/src/api/cluster/mod.rs | 142 +- packages/core/src/api/game/arguments.rs | 10 +- packages/core/src/api/game/launch.rs | 5 +- packages/core/src/api/game/metadata.rs | 10 +- packages/core/src/api/game/rules.rs | 5 +- packages/core/src/api/ingress.rs | 2 +- packages/core/src/api/java/mod.rs | 7 +- packages/core/src/api/processes.rs | 3 +- packages/core/src/api/proxy/event.rs | 4 +- packages/core/src/api/proxy/impl/mod.rs | 6 +- .../core/src/api/proxy/impl/proxy_empty.rs | 4 +- .../core/src/api/proxy/impl/proxy_tauri.rs | 19 +- packages/core/src/api/proxy/message.rs | 2 +- packages/core/src/api/setting_profiles.rs | 42 +- packages/core/src/store/credentials.rs | 4 +- packages/core/src/store/processes.rs | 121 +- packages/core/src/utils/io.rs | 7 +- packages/core_old/Cargo.toml | 92 - packages/core_old/package.json | 14 - .../src/api/cluster/content/logger.rs | 372 --- .../core_old/src/api/cluster/content/mod.rs | 4 - .../src/api/cluster/content/package.rs | 297 --- .../src/api/cluster/content/screenshots.rs | 23 - .../src/api/cluster/content/worlds.rs | 23 - packages/core_old/src/api/cluster/create.rs | 333 --- packages/core_old/src/api/cluster/mod.rs | 436 ---- packages/core_old/src/api/cluster/update.rs | 3 - packages/core_old/src/api/handler.rs | 42 - packages/core_old/src/api/ingress.rs | 5 - packages/core_old/src/api/java.rs | 148 -- packages/core_old/src/api/metadata.rs | 98 - packages/core_old/src/api/minecraft.rs | 87 - packages/core_old/src/api/mod.rs | 31 - .../src/api/package/content/curseforge.rs | 711 ----- .../core_old/src/api/package/content/mod.rs | 337 --- .../src/api/package/content/modrinth.rs | 535 ---- .../src/api/package/content/skyclient.rs | 363 --- packages/core_old/src/api/package/from.rs | 409 --- .../src/api/package/import/atlauncher.rs | 240 -- .../src/api/package/import/curseforge.rs | 168 -- .../core_old/src/api/package/import/ftb.rs | 2 - .../src/api/package/import/gdlauncher.rs | 109 - .../core_old/src/api/package/import/mod.rs | 318 --- .../src/api/package/import/modrinth.rs | 27 - .../src/api/package/import/multibased.rs | 307 --- .../src/api/package/import/technic.rs | 2 - .../src/api/package/import/tlauncher.rs | 2 - .../core_old/src/api/package/infer/mod.rs | 254 -- packages/core_old/src/api/package/mod.rs | 10 - packages/core_old/src/api/package/mrpack.rs | 338 --- packages/core_old/src/api/package/zip.rs | 1 - packages/core_old/src/api/processor.rs | 232 -- packages/core_old/src/api/proxy/mod.rs | 277 -- packages/core_old/src/api/proxy/send.rs | 255 -- packages/core_old/src/api/proxy/utils.rs | 78 - packages/core_old/src/api/settings.rs | 205 -- packages/core_old/src/constants.rs | 118 - packages/core_old/src/error.rs | 180 -- packages/core_old/src/game/arguments.rs | 361 --- packages/core_old/src/game/metadata.rs | 349 --- packages/core_old/src/game/mod.rs | 544 ---- packages/core_old/src/lib.rs | 20 - packages/core_old/src/logger.rs | 74 - packages/core_old/src/store/clusters.rs | 802 ------ packages/core_old/src/store/credentials.rs | 80 - packages/core_old/src/store/dirs.rs | 191 -- packages/core_old/src/store/discord.rs | 162 -- packages/core_old/src/store/java_versions.rs | 76 - packages/core_old/src/store/metadata.rs | 263 -- packages/core_old/src/store/minecraft.rs | 1043 -------- packages/core_old/src/store/mod.rs | 289 --- packages/core_old/src/store/oneconfig.rs | 39 - packages/core_old/src/store/package.rs | 969 ------- packages/core_old/src/store/processor.rs | 762 ------ packages/core_old/src/store/settings.rs | 209 -- packages/core_old/src/utils/crypto.rs | 68 - packages/core_old/src/utils/http.rs | 303 --- packages/core_old/src/utils/java.rs | 366 --- packages/core_old/src/utils/mod.rs | 15 - packages/core_old/src/utils/pagination.rs | 23 - packages/core_old/src/utils/watcher.rs | 86 - packages/core_old/src/utils/window.rs | 19 - packages/core_old/wrapper/README.md | 15 - packages/core_old/wrapper/forge/.gitkeep | 0 packages/core_old/wrapper/java/JavaInfo.java | 19 - packages/entity/src/domain/icon.rs | 5 +- packages/entity/src/domain/resolution.rs | 1 + packages/entity/src/domain/settings.rs | 3 + packages/entity/src/domain/utility.rs | 1 + packages/macro/Cargo.toml | 1 + packages/macro/src/specta.rs | 14 +- 102 files changed, 1992 insertions(+), 15574 deletions(-) create mode 100644 .clippy.toml delete mode 100644 packages/core_old/Cargo.toml delete mode 100644 packages/core_old/package.json delete mode 100644 packages/core_old/src/api/cluster/content/logger.rs delete mode 100644 packages/core_old/src/api/cluster/content/mod.rs delete mode 100644 packages/core_old/src/api/cluster/content/package.rs delete mode 100644 packages/core_old/src/api/cluster/content/screenshots.rs delete mode 100644 packages/core_old/src/api/cluster/content/worlds.rs delete mode 100644 packages/core_old/src/api/cluster/create.rs delete mode 100644 packages/core_old/src/api/cluster/mod.rs delete mode 100644 packages/core_old/src/api/cluster/update.rs delete mode 100644 packages/core_old/src/api/handler.rs delete mode 100644 packages/core_old/src/api/ingress.rs delete mode 100644 packages/core_old/src/api/java.rs delete mode 100644 packages/core_old/src/api/metadata.rs delete mode 100644 packages/core_old/src/api/minecraft.rs delete mode 100644 packages/core_old/src/api/mod.rs delete mode 100644 packages/core_old/src/api/package/content/curseforge.rs delete mode 100644 packages/core_old/src/api/package/content/mod.rs delete mode 100644 packages/core_old/src/api/package/content/modrinth.rs delete mode 100644 packages/core_old/src/api/package/content/skyclient.rs delete mode 100644 packages/core_old/src/api/package/from.rs delete mode 100644 packages/core_old/src/api/package/import/atlauncher.rs delete mode 100644 packages/core_old/src/api/package/import/curseforge.rs delete mode 100644 packages/core_old/src/api/package/import/ftb.rs delete mode 100644 packages/core_old/src/api/package/import/gdlauncher.rs delete mode 100644 packages/core_old/src/api/package/import/mod.rs delete mode 100644 packages/core_old/src/api/package/import/modrinth.rs delete mode 100644 packages/core_old/src/api/package/import/multibased.rs delete mode 100644 packages/core_old/src/api/package/import/technic.rs delete mode 100644 packages/core_old/src/api/package/import/tlauncher.rs delete mode 100644 packages/core_old/src/api/package/infer/mod.rs delete mode 100644 packages/core_old/src/api/package/mod.rs delete mode 100644 packages/core_old/src/api/package/mrpack.rs delete mode 100644 packages/core_old/src/api/package/zip.rs delete mode 100644 packages/core_old/src/api/processor.rs delete mode 100644 packages/core_old/src/api/proxy/mod.rs delete mode 100644 packages/core_old/src/api/proxy/send.rs delete mode 100644 packages/core_old/src/api/proxy/utils.rs delete mode 100644 packages/core_old/src/api/settings.rs delete mode 100644 packages/core_old/src/constants.rs delete mode 100644 packages/core_old/src/error.rs delete mode 100644 packages/core_old/src/game/arguments.rs delete mode 100644 packages/core_old/src/game/metadata.rs delete mode 100644 packages/core_old/src/game/mod.rs delete mode 100644 packages/core_old/src/lib.rs delete mode 100644 packages/core_old/src/logger.rs delete mode 100644 packages/core_old/src/store/clusters.rs delete mode 100644 packages/core_old/src/store/credentials.rs delete mode 100644 packages/core_old/src/store/dirs.rs delete mode 100644 packages/core_old/src/store/discord.rs delete mode 100644 packages/core_old/src/store/java_versions.rs delete mode 100644 packages/core_old/src/store/metadata.rs delete mode 100644 packages/core_old/src/store/minecraft.rs delete mode 100644 packages/core_old/src/store/mod.rs delete mode 100644 packages/core_old/src/store/oneconfig.rs delete mode 100644 packages/core_old/src/store/package.rs delete mode 100644 packages/core_old/src/store/processor.rs delete mode 100644 packages/core_old/src/store/settings.rs delete mode 100644 packages/core_old/src/utils/crypto.rs delete mode 100644 packages/core_old/src/utils/http.rs delete mode 100644 packages/core_old/src/utils/java.rs delete mode 100644 packages/core_old/src/utils/mod.rs delete mode 100644 packages/core_old/src/utils/pagination.rs delete mode 100644 packages/core_old/src/utils/watcher.rs delete mode 100644 packages/core_old/src/utils/window.rs delete mode 100644 packages/core_old/wrapper/README.md delete mode 100644 packages/core_old/wrapper/forge/.gitkeep delete mode 100644 packages/core_old/wrapper/java/JavaInfo.java diff --git a/.clippy.toml b/.clippy.toml new file mode 100644 index 00000000..458dcafa --- /dev/null +++ b/.clippy.toml @@ -0,0 +1 @@ +cognitive-complexity-threshold = 30 diff --git a/.rustfmt.toml b/.rustfmt.toml index 632534eb..07630d8b 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -12,3 +12,12 @@ use_try_shorthand = false use_field_init_shorthand = false force_explicit_abi = true unstable_features = true +deprecated = "warn" +deprecated_in_future = "forbid" +rust_2018_idioms = { level = "warn", priority = -1 } +trivial_casts = "warn" +trivial_numeric_casts = "warn" +unused_allocation = "warn" +unused_import_braces = "warn" +unused_must_use = "deny" +unused_qualifications = "warn" diff --git a/Cargo.lock b/Cargo.lock index 00dc8f33..a7491d5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,17 +23,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - [[package]] name = "ahash" version = "0.7.8" @@ -147,9 +136,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.89" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "arbitrary" @@ -160,6 +149,27 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "arboard" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1df21f715862ede32a0c525ce2ca4d52626bb0007f8c18b87a384503ac33e70" +dependencies = [ + "clipboard-win", + "image", + "log", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.1", + "parking_lot", + "percent-encoding", + "windows-sys 0.59.0", + "wl-clipboard-rs", + "x11rb", +] + [[package]] name = "arrayvec" version = "0.7.6" @@ -167,25 +177,170 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] -name = "async-compression" -version = "0.4.12" +name = "ashpd" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fec134f64e2bc57411226dfc4e52dec859ddfc7e711fc5e07b612584f000e4aa" +checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df" dependencies = [ - "brotli", + "enumflags2", + "futures-channel", + "futures-util", + "rand 0.9.1", + "raw-window-handle", + "serde", + "serde_repr", + "tokio", + "url", + "zbus", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-compression" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b37fc50485c4f3f736a4fb14199f6d5f5ba008d7f28fe710306c92780f004c07" +dependencies = [ + "brotli 8.0.0", "bzip2", "deflate64", "flate2", "futures-core", "futures-io", + "liblzma", "memchr", "pin-project-lite", "tokio", - "xz2", "zstd", "zstd-safe", ] +[[package]] +name = "async-executor" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 0.38.44", + "slab", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix 0.38.44", + "tracing", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "async-signal" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 0.38.44", + "signal-hook-registry", + "slab", + "windows-sys 0.59.0", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -205,18 +360,24 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] -name = "async-trait" -version = "0.1.83" +name = "async-task" +version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -359,12 +520,6 @@ dependencies = [ "wyz", ] -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - [[package]] name = "block-buffer" version = "0.10.4" @@ -380,7 +535,29 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" dependencies = [ - "objc2", + "objc2 0.5.2", +] + +[[package]] +name = "block2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2" +dependencies = [ + "objc2 0.6.1", +] + +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", ] [[package]] @@ -400,22 +577,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4918709cc4dd777ad2b6303ed03cb37f3ca0ccede8c1b0d28ac6db8f4710e0" dependencies = [ "once_cell", - "proc-macro-crate 2.0.2", + "proc-macro-crate 2.0.0", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", "syn_derive", ] [[package]] name = "brotli" -version = "6.0.0" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" +checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", - "brotli-decompressor", + "brotli-decompressor 4.0.1", +] + +[[package]] +name = "brotli" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf19e729cdbd51af9a397fb9ef8ac8378007b797f8273cfbfdf45dcaa316167b" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor 5.0.0", ] [[package]] @@ -428,6 +616,16 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -468,6 +666,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.10.1" @@ -479,22 +683,20 @@ dependencies = [ [[package]] name = "bzip2" -version = "0.4.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" dependencies = [ "bzip2-sys", - "libc", ] [[package]] name = "bzip2-sys" -version = "0.1.11+1.0.8" +version = "0.1.13+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" dependencies = [ "cc", - "libc", "pkg-config", ] @@ -543,26 +745,26 @@ dependencies = [ [[package]] name = "cargo_metadata" -version = "0.18.1" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" dependencies = [ "camino", "cargo-platform", "semver", "serde", "serde_json", - "thiserror 1.0.64", + "thiserror 2.0.12", ] [[package]] name = "cargo_toml" -version = "0.17.2" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a969e13a7589e9e3e4207e153bae624ade2b5622fb4684a4923b23ec3d57719" +checksum = "02260d489095346e5cafd04dea8e8cb54d1d74fcd759022a9b72986ebe9a1257" dependencies = [ "serde", - "toml 0.8.2", + "toml", ] [[package]] @@ -590,7 +792,7 @@ checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" dependencies = [ "byteorder", "fnv", - "uuid 1.10.0", + "uuid 1.16.0", ] [[package]] @@ -623,9 +825,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", @@ -633,17 +835,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.6", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", + "windows-link", ] [[package]] @@ -677,7 +869,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -687,33 +879,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] -name = "cocoa" -version = "0.26.0" +name = "clipboard-win" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2" +checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" dependencies = [ - "bitflags 2.6.0", - "block", - "cocoa-foundation", - "core-foundation 0.10.0", - "core-graphics", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14045fb83be07b5acf1c0884b2180461635b433455fa35d1cd6f17f1450679d" -dependencies = [ - "bitflags 2.6.0", - "block", - "core-foundation 0.10.0", - "core-graphics-types", - "libc", - "objc", + "error-code", ] [[package]] @@ -761,10 +932,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] -name = "constant_time_eq" -version = "0.3.1" +name = "const-random" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.15", + "once_cell", + "tiny-keccak", +] [[package]] name = "convert_case" @@ -772,6 +957,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "time", + "version_check", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -864,25 +1059,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "crossbeam-deque" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-queue" version = "0.3.12" @@ -898,6 +1074,12 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "crunchy" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -944,7 +1126,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -954,7 +1136,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" dependencies = [ "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -978,7 +1160,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -989,7 +1171,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -1027,7 +1209,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -1040,7 +1222,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -1057,34 +1239,35 @@ dependencies = [ [[package]] name = "dirs" -version = "5.0.1" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "discord-rich-presence" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f632a41e3e97febf8edff46b1405f9875894c29e20a25c5abe566872226b3f84" +checksum = "a75db747ecd252c01bfecaf709b07fcb4c634adf0edb5fed47bc9c3052e7076b" dependencies = [ "serde", "serde_derive", "serde_json", + "serde_repr", "uuid 0.8.2", ] @@ -1094,6 +1277,28 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +[[package]] +name = "dispatch2" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a0d569e003ff27784e0e14e4a594048698e0c0f0b66cabcb51511be55a7caa0" +dependencies = [ + "bitflags 2.6.0", + "block2 0.6.1", + "libc", + "objc2 0.6.1", +] + +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.6.0", + "objc2 0.6.1", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -1102,7 +1307,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -1125,7 +1330,16 @@ checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", +] + +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", ] [[package]] @@ -1134,6 +1348,12 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + [[package]] name = "dpi" version = "0.1.1" @@ -1215,14 +1435,14 @@ dependencies = [ [[package]] name = "embed-resource" -version = "2.5.0" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4e24052d7be71f0efb50c201557f6fe7d237cfd5a64fd5bcd7fd8fe32dbbffa" +checksum = "7fbc6e0d8e0c03a655b53ca813f0463d2c956bc4db8138dbc89f120b066551e3" dependencies = [ "cc", "memchr", "rustc_version", - "toml 0.8.2", + "toml", "vswhom", "winreg", ] @@ -1248,6 +1468,33 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enumflags2" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba2f4b465f5318854c6f8dd686ede6c0a9dc67d4b1ac241cf0eb51521a309147" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -1274,6 +1521,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "error-code" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" + [[package]] name = "etcetera" version = "0.8.0" @@ -1296,6 +1549,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "fastrand" version = "2.1.1" @@ -1331,6 +1594,24 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.0.34" @@ -1341,15 +1622,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "fluent-uri" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "flume" version = "0.11.0" @@ -1391,7 +1663,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -1505,7 +1777,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -1657,6 +1929,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + [[package]] name = "getrandom" version = "0.1.16" @@ -1759,11 +2041,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" dependencies = [ "heck 0.4.1", - "proc-macro-crate 2.0.2", + "proc-macro-crate 2.0.0", "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -1853,7 +2135,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -1928,6 +2210,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hex" version = "0.4.3" @@ -2103,9 +2391,9 @@ dependencies = [ [[package]] name = "ico" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3804960be0bb5e4edb1e1ad67afd321a9ecfd875c3e65c099468fd2717d7cae" +checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98" dependencies = [ "byteorder", "png", @@ -2127,6 +2415,19 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "image" +version = "0.25.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" +dependencies = [ + "bytemuck", + "byteorder-lite", + "num-traits", + "png", + "tiff", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -2164,9 +2465,9 @@ dependencies = [ [[package]] name = "infer" -version = "0.16.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc150e5ce2330295b8616ce0e3f53250e53af31759a9dbedad1621ba29151847" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" dependencies = [ "cfb", ] @@ -2179,16 +2480,7 @@ checksum = "6c38228f24186d9cc68c729accb4d413be9eaed6ad07ff79e0270d9e56f3de13" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", -] - -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array", + "syn 2.0.101", ] [[package]] @@ -2292,6 +2584,12 @@ dependencies = [ "libc", ] +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + [[package]] name = "js-sys" version = "0.3.70" @@ -2303,9 +2601,9 @@ dependencies = [ [[package]] name = "json-patch" -version = "2.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b1fb8864823fad91877e6caea0baca82e49e8db50f8e5c9f9a453e27d3330fc" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" dependencies = [ "jsonptr", "serde", @@ -2315,11 +2613,10 @@ dependencies = [ [[package]] name = "jsonptr" -version = "0.4.7" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c6e529149475ca0b2820835d3dce8fcc41c6b943ca608d32f35b449255e4627" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" dependencies = [ - "fluent-uri", "serde", "serde_json", ] @@ -2397,6 +2694,26 @@ dependencies = [ "winapi", ] +[[package]] +name = "liblzma" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66352d7a8ac12d4877b6e6ea5a9b7650ee094257dc40889955bea5bc5b08c1d0" +dependencies = [ + "liblzma-sys", +] + +[[package]] +name = "liblzma-sys" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5839bad90c3cc2e0b8c4ed8296b80e86040240f81d46b9c0e9bc8dd51ddd3af1" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "libm" version = "0.2.11" @@ -2411,6 +2728,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", + "redox_syscall", ] [[package]] @@ -2424,6 +2742,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + [[package]] name = "linux-raw-sys" version = "0.9.3" @@ -2440,54 +2764,18 @@ dependencies = [ "scopeguard", ] -[[package]] -name = "lockfree-object-pool" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" - [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" -[[package]] -name = "lzma-rs" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" -dependencies = [ - "byteorder", - "crc", -] - -[[package]] -name = "lzma-sys" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - [[package]] name = "mac" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - [[package]] name = "markup5ever" version = "0.11.0" @@ -2561,7 +2849,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -2580,6 +2868,18 @@ dependencies = [ "unicase", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "minisign-verify" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6367d84fb54d4242af283086402907277715b8fe46976963af5ebf173f8efba3" + [[package]] name = "miniz_oxide" version = "0.8.0" @@ -2596,7 +2896,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", @@ -2604,21 +2904,22 @@ dependencies = [ [[package]] name = "muda" -version = "0.15.1" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8123dfd4996055ac9b15a60ad263b44b01e539007523ad7a4a533a3d93b0591" +checksum = "4de14a9b5d569ca68d7c891d613b390cf5ab4f851c77aaa2f9e435555d3d9492" dependencies = [ "crossbeam-channel", "dpi", "gtk", "keyboard-types", - "objc2", + "objc2 0.6.1", "objc2-app-kit", - "objc2-foundation", + "objc2-core-foundation", + "objc2-foundation 0.3.1", "once_cell", "png", "serde", - "thiserror 1.0.64", + "thiserror 2.0.12", "windows-sys 0.59.0", ] @@ -2664,12 +2965,35 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "cfg_aliases 0.2.1", + "libc", + "memoffset", +] + [[package]] name = "nodrop" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "ntapi" version = "0.4.1" @@ -2767,10 +3091,10 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 2.0.2", + "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -2779,23 +3103,11 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - [[package]] name = "objc-sys" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" -dependencies = [ - "cc", -] [[package]] name = "objc2" @@ -2808,86 +3120,104 @@ dependencies = [ ] [[package]] -name = "objc2-app-kit" -version = "0.2.2" +name = "objc2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" dependencies = [ "bitflags 2.6.0", - "block2", + "block2 0.6.1", "libc", - "objc2", + "objc2 0.6.1", + "objc2-cloud-kit", "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", "objc2-core-image", - "objc2-foundation", - "objc2-quartz-core", + "objc2-foundation 0.3.1", + "objc2-quartz-core 0.3.1", ] [[package]] name = "objc2-cloud-kit" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +checksum = "17614fdcd9b411e6ff1117dfb1d0150f908ba83a7df81b1f118005fe0a8ea15d" dependencies = [ "bitflags 2.6.0", - "block2", - "objc2", - "objc2-core-location", - "objc2-foundation", -] - -[[package]] -name = "objc2-contacts" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" -dependencies = [ - "block2", - "objc2", - "objc2-foundation", + "objc2 0.6.1", + "objc2-foundation 0.3.1", ] [[package]] name = "objc2-core-data" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +checksum = "291fbbf7d29287518e8686417cf7239c74700fd4b607623140a7d4a3c834329d" dependencies = [ "bitflags 2.6.0", - "block2", - "objc2", - "objc2-foundation", + "objc2 0.6.1", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +dependencies = [ + "bitflags 2.6.0", + "dispatch2 0.3.0", + "objc2 0.6.1", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" +dependencies = [ + "bitflags 2.6.0", + "dispatch2 0.3.0", + "objc2 0.6.1", + "objc2-core-foundation", + "objc2-io-surface", ] [[package]] name = "objc2-core-image" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +checksum = "79b3dc0cc4386b6ccf21c157591b34a7f44c8e75b064f85502901ab2188c007e" dependencies = [ - "block2", - "objc2", - "objc2-foundation", - "objc2-metal", -] - -[[package]] -name = "objc2-core-location" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" -dependencies = [ - "block2", - "objc2", - "objc2-contacts", - "objc2-foundation", + "objc2 0.6.1", + "objc2-foundation 0.3.1", ] [[package]] name = "objc2-encode" -version = "4.0.3" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] [[package]] name = "objc2-foundation" @@ -2896,21 +3226,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ "bitflags 2.6.0", - "block2", + "block2 0.5.1", "libc", - "objc2", + "objc2 0.5.2", ] [[package]] -name = "objc2-link-presentation" -version = "0.2.2" +name = "objc2-foundation" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ - "block2", - "objc2", - "objc2-app-kit", - "objc2-foundation", + "bitflags 2.6.0", + "block2 0.6.1", + "libc", + "objc2 0.6.1", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" +dependencies = [ + "bitflags 2.6.0", + "objc2 0.6.1", + "objc2-core-foundation", ] [[package]] @@ -2920,9 +3262,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ "bitflags 2.6.0", - "block2", - "objc2", - "objc2-foundation", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-osa-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26bb88504b5a050dbba515d2414607bf5e57dd56b107bc5f0351197a3e7bdc5d" +dependencies = [ + "bitflags 2.6.0", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-foundation 0.3.1", ] [[package]] @@ -2932,78 +3286,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ "bitflags 2.6.0", - "block2", - "objc2", - "objc2-foundation", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", "objc2-metal", ] [[package]] -name = "objc2-symbols" -version = "0.2.2" +name = "objc2-quartz-core" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5" dependencies = [ - "objc2", - "objc2-foundation", + "bitflags 2.6.0", + "objc2 0.6.1", + "objc2-foundation 0.3.1", ] [[package]] name = "objc2-ui-kit" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +checksum = "25b1312ad7bc8a0e92adae17aa10f90aae1fb618832f9b993b022b591027daed" dependencies = [ "bitflags 2.6.0", - "block2", - "objc2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-image", - "objc2-core-location", - "objc2-foundation", - "objc2-link-presentation", - "objc2-quartz-core", - "objc2-symbols", - "objc2-uniform-type-identifiers", - "objc2-user-notifications", -] - -[[package]] -name = "objc2-uniform-type-identifiers" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" -dependencies = [ - "block2", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-user-notifications" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" -dependencies = [ - "bitflags 2.6.0", - "block2", - "objc2", - "objc2-core-location", - "objc2-foundation", + "objc2 0.6.1", + "objc2-core-foundation", + "objc2-foundation 0.3.1", ] [[package]] name = "objc2-web-kit" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68bc69301064cebefc6c4c90ce9cba69225239e4b8ff99d445a2b5563797da65" +checksum = "91672909de8b1ce1c2252e95bbee8c1649c9ad9d14b9248b3d7b4c47903c47ad" dependencies = [ "bitflags 2.6.0", - "block2", - "objc2", + "block2 0.6.1", + "objc2 0.6.1", "objc2-app-kit", - "objc2-foundation", + "objc2-core-foundation", + "objc2-foundation 0.3.1", ] [[package]] @@ -3017,9 +3340,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "onelauncher_core" @@ -3065,7 +3388,7 @@ dependencies = [ "tauri", "tauri-specta", "tempfile", - "thiserror 1.0.64", + "thiserror 2.0.12", "tokio", "tokio-stream", "tokio-util", @@ -3074,9 +3397,8 @@ dependencies = [ "tracing-error", "tracing-subscriber", "url", - "uuid 1.10.0", + "uuid 1.16.0", "whoami", - "zip", ] [[package]] @@ -3092,7 +3414,7 @@ dependencies = [ "serde_json", "specta", "url", - "uuid 1.10.0", + "uuid 1.16.0", ] [[package]] @@ -3103,12 +3425,42 @@ dependencies = [ "libc", ] +[[package]] +name = "onelauncher_gui" +version = "0.2.0-alpha.2" +dependencies = [ + "anyhow", + "chrono", + "interpulse", + "onelauncher_core", + "serde", + "serde_json", + "specta", + "specta-typescript", + "strum 0.27.1", + "tauri", + "tauri-build", + "tauri-plugin-clipboard-manager", + "tauri-plugin-deep-link", + "tauri-plugin-dialog", + "tauri-plugin-single-instance", + "tauri-plugin-updater", + "tauri-plugin-window-state", + "tauri-specta", + "thiserror 2.0.12", + "tokio", + "tracing", + "tracing-error", + "uuid 1.16.0", +] + [[package]] name = "onelauncher_macro" version = "0.2.0-alpha.2" dependencies = [ + "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -3136,6 +3488,50 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list", + "hashbrown 0.14.5", +] + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "os_pipe" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "osakit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b" +dependencies = [ + "objc2 0.6.1", + "objc2-foundation 0.3.1", + "objc2-osa-kit", + "serde", + "serde_json", + "thiserror 2.0.12", +] + [[package]] name = "ouroboros" version = "0.18.5" @@ -3157,7 +3553,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -3238,16 +3634,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "pbkdf2" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" -dependencies = [ - "digest", - "hmac", -] - [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -3263,6 +3649,16 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap 2.5.0", +] + [[package]] name = "pgvector" version = "0.4.0" @@ -3376,7 +3772,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -3423,7 +3819,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -3438,6 +3834,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkcs1" version = "0.7.5" @@ -3473,7 +3880,7 @@ checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" dependencies = [ "base64 0.22.1", "indexmap 2.5.0", - "quick-xml", + "quick-xml 0.32.0", "serde", "time", ] @@ -3491,6 +3898,21 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "polling" +version = "3.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite", + "rustix 0.38.44", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "portable-atomic" version = "1.8.0" @@ -3539,14 +3961,22 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "2.0.2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" dependencies = [ - "toml_datetime", "toml_edit 0.20.2", ] +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit 0.22.26", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -3590,7 +4020,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -3601,9 +4031,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -3616,7 +4046,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", "version_check", "yansi", ] @@ -3650,6 +4080,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + [[package]] name = "quinn" version = "0.11.5" @@ -3700,9 +4139,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] @@ -3744,6 +4183,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -3764,6 +4213,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -3782,6 +4241,15 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.2", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -3806,26 +4274,6 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - [[package]] name = "redox_syscall" version = "0.5.6" @@ -3837,13 +4285,13 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.4.6" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ "getrandom 0.2.15", "libredox", - "thiserror 1.0.64", + "thiserror 2.0.12", ] [[package]] @@ -3946,7 +4394,7 @@ dependencies = [ "wasm-streams", "web-sys", "webpki-roots", - "windows-registry", + "windows-registry 0.2.0", ] [[package]] @@ -3959,6 +4407,31 @@ dependencies = [ "subtle", ] +[[package]] +name = "rfd" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80c844748fdc82aae252ee4594a89b6e7ebef1063de7951545564cbc4e57075d" +dependencies = [ + "ashpd", + "block2 0.6.1", + "dispatch2 0.2.0", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "log", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.1", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.59.0", +] + [[package]] name = "ring" version = "0.17.8" @@ -3989,7 +4462,7 @@ dependencies = [ "rkyv_derive", "seahash", "tinyvec", - "uuid 1.10.0", + "uuid 1.16.0", ] [[package]] @@ -4023,6 +4496,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rust-ini" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e310ef0e1b6eeb79169a1171daf9abcb87a2e17c03bee2c4bb100b55c75409f" +dependencies = [ + "cfg-if", + "ordered-multimap", + "trim-in-place", +] + [[package]] name = "rust_decimal" version = "1.37.1" @@ -4060,6 +4544,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + [[package]] name = "rustix" version = "1.0.3" @@ -4069,7 +4566,7 @@ dependencies = [ "bitflags 2.6.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.9.3", "windows-sys 0.59.0", ] @@ -4114,6 +4611,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + [[package]] name = "ryu" version = "1.0.18" @@ -4150,7 +4653,7 @@ dependencies = [ "serde", "serde_json", "url", - "uuid 1.10.0", + "uuid 1.16.0", ] [[package]] @@ -4162,7 +4665,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -4181,7 +4684,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -4205,12 +4708,12 @@ dependencies = [ "serde", "serde_json", "sqlx", - "strum", + "strum 0.26.3", "thiserror 1.0.64", "time", "tracing", "url", - "uuid 1.10.0", + "uuid 1.16.0", ] [[package]] @@ -4240,7 +4743,7 @@ dependencies = [ "proc-macro2", "quote", "sea-bae", - "syn 2.0.87", + "syn 2.0.101", "unicode-ident", ] @@ -4274,7 +4777,7 @@ dependencies = [ "sea-query-derive", "serde_json", "time", - "uuid 1.10.0", + "uuid 1.16.0", ] [[package]] @@ -4290,7 +4793,7 @@ dependencies = [ "serde_json", "sqlx", "time", - "uuid 1.10.0", + "uuid 1.16.0", ] [[package]] @@ -4303,7 +4806,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", "thiserror 2.0.12", ] @@ -4327,7 +4830,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -4381,9 +4884,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.210" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] @@ -4401,13 +4904,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -4418,14 +4921,14 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa 1.0.11", "memchr", @@ -4460,7 +4963,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -4511,7 +5014,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -4660,9 +5163,9 @@ dependencies = [ "foreign-types", "js-sys", "log", - "objc2", - "objc2-foundation", - "objc2-quartz-core", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", "raw-window-handle", "redox_syscall", "wasm-bindgen", @@ -4698,9 +5201,8 @@ dependencies = [ [[package]] name = "specta" -version = "2.0.0-rc.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ccbb212565d2dc177bc15ecb7b039d66c4490da892436a4eee5b394d620c9bc" +version = "2.0.0-rc.22" +source = "git+https://github.com/specta-rs/specta?rev=30661263250e7b6f9b36cdc56e6c843ac5abb226#30661263250e7b6f9b36cdc56e6c843ac5abb226" dependencies = [ "chrono", "paste", @@ -4708,38 +5210,25 @@ dependencies = [ "serde_json", "specta-macros", "thiserror 1.0.64", - "time", - "uuid 1.10.0", -] - -[[package]] -name = "specta-jsdoc" -version = "0.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831df54bbb99fe669440885cde83531e4e1e617b48b444ed5739d3d8478890f8" -dependencies = [ - "specta", - "specta-serde", - "specta-typescript", + "tokio", + "uuid 1.16.0", ] [[package]] name = "specta-macros" -version = "2.0.0-rc.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68999d29816965eb9e5201f60aec02a76512139811661a7e8e653abc810b8f72" +version = "2.0.0-rc.18" +source = "git+https://github.com/specta-rs/specta?rev=30661263250e7b6f9b36cdc56e6c843ac5abb226#30661263250e7b6f9b36cdc56e6c843ac5abb226" dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] name = "specta-serde" -version = "0.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12260cbb21abb2e83a0375b1521867910e3aed8a7afa782206150ce552cd2e5a" +version = "0.0.9" +source = "git+https://github.com/specta-rs/specta?rev=30661263250e7b6f9b36cdc56e6c843ac5abb226#30661263250e7b6f9b36cdc56e6c843ac5abb226" dependencies = [ "specta", "thiserror 1.0.64", @@ -4747,9 +5236,8 @@ dependencies = [ [[package]] name = "specta-typescript" -version = "0.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4472229365ceb6395487e3a60d921ad8e21f9ad06eaecc396f098902c9adc" +version = "0.0.9" +source = "git+https://github.com/specta-rs/specta?rev=30661263250e7b6f9b36cdc56e6c843ac5abb226#30661263250e7b6f9b36cdc56e6c843ac5abb226" dependencies = [ "specta", "specta-serde", @@ -4825,7 +5313,7 @@ dependencies = [ "tokio-stream", "tracing", "url", - "uuid 1.10.0", + "uuid 1.16.0", "webpki-roots", ] @@ -4839,7 +5327,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -4862,7 +5350,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.87", + "syn 2.0.101", "tempfile", "tokio", "url", @@ -4911,7 +5399,7 @@ dependencies = [ "thiserror 2.0.12", "time", "tracing", - "uuid 1.10.0", + "uuid 1.16.0", "whoami", ] @@ -4954,7 +5442,7 @@ dependencies = [ "thiserror 2.0.12", "time", "tracing", - "uuid 1.10.0", + "uuid 1.16.0", "whoami", ] @@ -4981,7 +5469,7 @@ dependencies = [ "time", "tracing", "url", - "uuid 1.10.0", + "uuid 1.16.0", ] [[package]] @@ -5045,6 +5533,28 @@ version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +[[package]] +name = "strum" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.101", +] + [[package]] name = "subtle" version = "2.6.1" @@ -5075,9 +5585,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.87" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -5093,7 +5603,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -5107,15 +5617,14 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.31.4" +version = "0.34.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "355dbe4f8799b304b05e1b0f05fc59b2a18d36645cf169607da45bde2f69a1be" +checksum = "a4b93974b3d3aeaa036504b8eefd4c039dced109171c1ae973f1dc63b2c7e4b2" dependencies = [ - "core-foundation-sys", "libc", "memchr", "ntapi", - "rayon", + "objc2-core-foundation", "serde", "windows 0.57.0", ] @@ -5150,18 +5659,17 @@ dependencies = [ "cfg-expr", "heck 0.5.0", "pkg-config", - "toml 0.8.2", + "toml", "version-compare", ] [[package]] name = "tao" -version = "0.30.2" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e48d7c56b3f7425d061886e8ce3b6acfab1993682ed70bef50fd133d721ee6" +checksum = "1e59c1f38e657351a2e822eadf40d6a2ad4627b9c25557bc1180ec1b3295ef82" dependencies = [ "bitflags 2.6.0", - "cocoa", "core-foundation 0.10.0", "core-graphics", "crossbeam-channel", @@ -5171,7 +5679,6 @@ dependencies = [ "gdkwayland-sys", "gdkx11-sys", "gtk", - "instant", "jni", "lazy_static", "libc", @@ -5179,7 +5686,9 @@ dependencies = [ "ndk", "ndk-context", "ndk-sys", - "objc", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-foundation 0.3.1", "once_cell", "parking_lot", "raw-window-handle", @@ -5187,8 +5696,8 @@ dependencies = [ "tao-macros", "unicode-segmentation", "url", - "windows 0.58.0", - "windows-core 0.58.0", + "windows 0.61.1", + "windows-core 0.61.0", "windows-version", "x11-dl", ] @@ -5201,7 +5710,7 @@ checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -5210,6 +5719,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tar" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "target-lexicon" version = "0.12.16" @@ -5218,9 +5738,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.0.4" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44438500b50708bfc1e6083844e135d1b516325aae58710dcd8fb67e050ae87c" +checksum = "e7b0bc1aec81bda6bc455ea98fcaed26b3c98c1648c627ad6ff1c704e8bf8cbc" dependencies = [ "anyhow", "bytes", @@ -5234,14 +5754,16 @@ dependencies = [ "heck 0.5.0", "http", "http-range", + "image", "jni", "libc", "log", "mime", "muda", - "objc2", + "objc2 0.6.1", "objc2-app-kit", - "objc2-foundation", + "objc2-foundation 0.3.1", + "objc2-ui-kit", "percent-encoding", "plist", "raw-window-handle", @@ -5257,24 +5779,24 @@ dependencies = [ "tauri-runtime", "tauri-runtime-wry", "tauri-utils", - "thiserror 1.0.64", + "thiserror 2.0.12", "tokio", "tracing", "tray-icon", "url", "urlpattern", - "uuid 1.10.0", + "uuid 1.16.0", "webkit2gtk", "webview2-com", "window-vibrancy", - "windows 0.58.0", + "windows 0.61.1", ] [[package]] name = "tauri-build" -version = "2.0.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "935f9b3c49b22b3e2e485a57f46d61cd1ae07b1cbb2ba87387a387caf2d8c4e7" +checksum = "d7a0350f0df1db385ca5c02888a83e0e66655c245b7443db8b78a70da7d7f8fc" dependencies = [ "anyhow", "cargo_toml", @@ -5288,18 +5810,18 @@ dependencies = [ "serde_json", "tauri-utils", "tauri-winres", - "toml 0.8.2", + "toml", "walkdir", ] [[package]] name = "tauri-codegen" -version = "2.0.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95d7443dd4f0b597704b6a14b964ee2ed16e99928d8e6292ae9825f09fbcd30e" +checksum = "f93f035551bf7b11b3f51ad9bc231ebbe5e085565527991c16cf326aa38cdf47" dependencies = [ "base64 0.22.1", - "brotli", + "brotli 7.0.0", "ico", "json-patch", "plist", @@ -5310,61 +5832,220 @@ dependencies = [ "serde", "serde_json", "sha2", - "syn 2.0.87", + "syn 2.0.101", "tauri-utils", - "thiserror 1.0.64", + "thiserror 2.0.12", "time", "url", - "uuid 1.10.0", + "uuid 1.16.0", "walkdir", ] [[package]] name = "tauri-macros" -version = "2.0.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d2c0963ccfc3f5194415f2cce7acc975942a8797fbabfb0aa1ed6f59326ae7f" +checksum = "8db4df25e2d9d45de0c4c910da61cd5500190da14ae4830749fee3466dddd112" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", "tauri-codegen", "tauri-utils", ] [[package]] -name = "tauri-runtime" -version = "2.1.0" +name = "tauri-plugin" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8f437293d6f5e5dce829250f4dbdce4e0b52905e297a6689cc2963eb53ac728" +checksum = "37a5ebe6a610d1b78a94650896e6f7c9796323f408800cef436e0fa0539de601" dependencies = [ + "anyhow", + "glob", + "plist", + "schemars", + "serde", + "serde_json", + "tauri-utils", + "toml", + "walkdir", +] + +[[package]] +name = "tauri-plugin-clipboard-manager" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab4cb42fdf745229b768802e9180920a4be63122cf87ed1c879103f7609d98e" +dependencies = [ + "arboard", + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", +] + +[[package]] +name = "tauri-plugin-deep-link" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dba4412f30eaff6f5d210e20383c2d6835593977402092e95b72497a4f8632fa" +dependencies = [ + "dunce", + "rust-ini", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.12", + "tracing", + "url", + "windows-registry 0.5.1", + "windows-result 0.3.2", +] + +[[package]] +name = "tauri-plugin-dialog" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcaf6e5d6062423a0f711a23c2a573ccba222b6a16a9322d8499928f27e41376" +dependencies = [ + "log", + "raw-window-handle", + "rfd", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror 2.0.12", + "url", +] + +[[package]] +name = "tauri-plugin-fs" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88371e340ad2f07409a3b68294abe73f20bc9c1bc1b631a31dc37a3d0161f682" +dependencies = [ + "anyhow", + "dunce", + "glob", + "percent-encoding", + "schemars", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.12", + "toml", + "url", + "uuid 1.16.0", +] + +[[package]] +name = "tauri-plugin-single-instance" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1320af4d866a7fb5f5721d299d14d0dd9e4e6bc0359ff3e263124a2bf6814efa" +dependencies = [ + "serde", + "serde_json", + "tauri", + "thiserror 2.0.12", + "tracing", + "windows-sys 0.59.0", + "zbus", +] + +[[package]] +name = "tauri-plugin-updater" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f05c38afd77a4b8fd98e8fb6f1cdbb5fbb8a46ba181eb2758b05321e3c6209" +dependencies = [ + "base64 0.22.1", + "dirs", + "flate2", + "futures-util", + "http", + "infer", + "log", + "minisign-verify", + "osakit", + "percent-encoding", + "reqwest", + "semver", + "serde", + "serde_json", + "tar", + "tauri", + "tauri-plugin", + "tempfile", + "thiserror 2.0.12", + "time", + "tokio", + "url", + "windows-sys 0.59.0", + "zip", +] + +[[package]] +name = "tauri-plugin-window-state" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a27a3fe49de72adbe0d84aee33c89a0b059722cd0b42aaeab29eaaee7f7535cd" +dependencies = [ + "bitflags 2.6.0", + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", +] + +[[package]] +name = "tauri-runtime" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f004905d549854069e6774533d742b03cacfd6f03deb08940a8677586cbe39" +dependencies = [ + "cookie", "dpi", "gtk", "http", "jni", + "objc2 0.6.1", + "objc2-ui-kit", "raw-window-handle", "serde", "serde_json", "tauri-utils", - "thiserror 1.0.64", + "thiserror 2.0.12", "url", - "windows 0.58.0", + "windows 0.61.1", ] [[package]] name = "tauri-runtime-wry" -version = "2.1.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1431602bcc71f2f840ad623915c9842ecc32999b867c4a787d975a17a9625cc6" +checksum = "f85d056f4d4b014fe874814034f3416d57114b617a493a4fe552580851a3f3a2" dependencies = [ "gtk", "http", "jni", "log", - "objc2", + "objc2 0.6.1", "objc2-app-kit", - "objc2-foundation", + "objc2-foundation 0.3.1", + "once_cell", "percent-encoding", "raw-window-handle", "softbuffer", @@ -5375,25 +6056,23 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows 0.58.0", + "windows 0.61.1", "wry", ] [[package]] name = "tauri-specta" -version = "2.0.0-rc.20" +version = "2.0.0-rc.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d06336a2fa3ead0c8d08719e78cbc1fb73650845789605bd2ab908cbde72815" +checksum = "b23c0132dd3cf6064e5cd919b82b3f47780e9280e7b5910babfe139829b76655" dependencies = [ "heck 0.5.0", "serde", "serde_json", "specta", - "specta-jsdoc", - "specta-typescript", "tauri", "tauri-specta-macros", - "thiserror 1.0.64", + "thiserror 2.0.12", ] [[package]] @@ -5405,21 +6084,23 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] name = "tauri-utils" -version = "2.0.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c38b0230d6880cf6dd07b6d7dd7789a0869f98ac12146e0d18d1c1049215a045" +checksum = "b2900399c239a471bcff7f15c4399eb1a8c4fe511ba2853e07c996d771a5e0a4" dependencies = [ - "brotli", + "anyhow", + "brotli 7.0.0", "cargo_metadata", "ctor", "dunce", "glob", "html5ever", + "http", "infer", "json-patch", "kuchikiki", @@ -5436,22 +6117,23 @@ dependencies = [ "serde_json", "serde_with", "swift-rs", - "thiserror 1.0.64", - "toml 0.8.2", + "thiserror 2.0.12", + "toml", "url", "urlpattern", - "uuid 1.10.0", + "uuid 1.16.0", "walkdir", ] [[package]] name = "tauri-winres" -version = "0.1.1" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5993dc129e544393574288923d1ec447c857f3f644187f4fbf7d9a875fbfc4fb" +checksum = "e8d321dbc6f998d825ab3f0d62673e810c861aac2d0de2cc2c395328f1d113b4" dependencies = [ "embed-resource", - "toml 0.7.8", + "indexmap 2.5.0", + "toml", ] [[package]] @@ -5463,7 +6145,7 @@ dependencies = [ "fastrand", "getrandom 0.3.2", "once_cell", - "rustix", + "rustix 1.0.3", "windows-sys 0.59.0", ] @@ -5510,7 +6192,7 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -5521,7 +6203,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -5534,6 +6216,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "time" version = "0.3.36" @@ -5565,6 +6258,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -5582,9 +6284,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" dependencies = [ "backtrace", "bytes", @@ -5595,18 +6297,19 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", + "tracing", "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -5647,33 +6350,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.8" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.19.15", -] - -[[package]] -name = "toml" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.20.2", + "toml_edit 0.22.26", ] [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" dependencies = [ "serde", ] @@ -5685,10 +6376,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap 2.5.0", - "serde", - "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.5.40", ] [[package]] @@ -5696,14 +6385,32 @@ name = "toml_edit" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.5.0", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ "indexmap 2.5.0", "serde", "serde_spanned", "toml_datetime", - "winnow", + "toml_write", + "winnow 0.7.7", ] +[[package]] +name = "toml_write" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" + [[package]] name = "tower-service" version = "0.3.3" @@ -5742,7 +6449,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -5797,25 +6504,45 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.19.1" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c92af36a182b46206723bdf8a7942e20838cde1cf062e5b97854d57eb01763b" +checksum = "9f7eee98ec5c90daf179d55c20a49d8c0d043054ce7c26336c09a24d31f14fa0" dependencies = [ - "core-graphics", "crossbeam-channel", "dirs", "libappindicator", "muda", - "objc2", + "objc2 0.6.1", "objc2-app-kit", - "objc2-foundation", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.1", "once_cell", "png", "serde", - "thiserror 1.0.64", + "thiserror 2.0.12", "windows-sys 0.59.0", ] +[[package]] +name = "tree_magic_mini" +version = "3.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac5e8971f245c3389a5a76e648bfc80803ae066a1243a75db0064d7c1129d63" +dependencies = [ + "fnv", + "memchr", + "nom", + "once_cell", + "petgraph", +] + +[[package]] +name = "trim-in-place" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" + [[package]] name = "try-lock" version = "0.2.5" @@ -5834,6 +6561,17 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + [[package]] name = "unic-char-property" version = "0.9.0" @@ -5976,11 +6714,11 @@ dependencies = [ [[package]] name = "uuid" -version = "1.10.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.3.2", "serde", ] @@ -6096,7 +6834,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", "wasm-bindgen-shared", ] @@ -6130,7 +6868,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6154,6 +6892,76 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wayland-backend" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" +dependencies = [ + "cc", + "downcast-rs", + "rustix 0.38.44", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" +dependencies = [ + "bitflags 2.6.0", + "rustix 0.38.44", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" +dependencies = [ + "bitflags 2.6.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf" +dependencies = [ + "bitflags 2.6.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" +dependencies = [ + "proc-macro2", + "quick-xml 0.37.5", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" +dependencies = [ + "pkg-config", +] + [[package]] name = "web-sys" version = "0.3.70" @@ -6219,16 +7027,16 @@ dependencies = [ [[package]] name = "webview2-com" -version = "0.33.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61ff3d9d0ee4efcb461b14eb3acfda2702d10dc329f339303fc3e57215ae2c" +checksum = "b542b5cfbd9618c46c2784e4d41ba218c336ac70d44c55e47b251033e7d85601" dependencies = [ "webview2-com-macros", "webview2-com-sys", - "windows 0.58.0", - "windows-core 0.58.0", - "windows-implement 0.58.0", - "windows-interface 0.58.0", + "windows 0.61.1", + "windows-core 0.61.0", + "windows-implement 0.60.0", + "windows-interface 0.59.1", ] [[package]] @@ -6239,25 +7047,31 @@ checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] name = "webview2-com-sys" -version = "0.33.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3a3e2eeb58f82361c93f9777014668eb3d07e7d174ee4c819575a9208011886" +checksum = "8ae2d11c4a686e4409659d7891791254cf9286d3cfe0eef54df1523533d22295" dependencies = [ - "thiserror 1.0.64", - "windows 0.58.0", - "windows-core 0.58.0", + "thiserror 2.0.12", + "windows 0.61.1", + "windows-core 0.61.0", ] [[package]] -name = "whoami" -version = "1.5.2" +name = "weezl" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + +[[package]] +name = "whoami" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" dependencies = [ "redox_syscall", "wasite", @@ -6297,13 +7111,14 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "window-vibrancy" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ea403deff7b51fff19e261330f71608ff2cdef5721d72b64180bb95be7c4150" +checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" dependencies = [ - "objc2", + "objc2 0.6.1", "objc2-app-kit", - "objc2-foundation", + "objc2-core-foundation", + "objc2-foundation 0.3.1", "raw-window-handle", "windows-sys 0.59.0", "windows-version", @@ -6321,12 +7136,24 @@ dependencies = [ [[package]] name = "windows" -version = "0.58.0" +version = "0.61.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" dependencies = [ - "windows-core 0.58.0", - "windows-targets 0.52.6", + "windows-collections", + "windows-core 0.61.0", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.0", ] [[package]] @@ -6352,15 +7179,25 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.58.0" +version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" dependencies = [ - "windows-implement 0.58.0", - "windows-interface 0.58.0", - "windows-result 0.2.0", - "windows-strings", - "windows-targets 0.52.6", + "windows-implement 0.60.0", + "windows-interface 0.59.1", + "windows-link", + "windows-result 0.3.2", + "windows-strings 0.4.0", +] + +[[package]] +name = "windows-future" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32" +dependencies = [ + "windows-core 0.61.0", + "windows-link", ] [[package]] @@ -6371,18 +7208,18 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] name = "windows-implement" -version = "0.58.0" +version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -6393,18 +7230,34 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] name = "windows-interface" -version = "0.58.0" +version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.0", + "windows-link", ] [[package]] @@ -6414,10 +7267,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ "windows-result 0.2.0", - "windows-strings", + "windows-strings 0.1.0", "windows-targets 0.52.6", ] +[[package]] +name = "windows-registry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1da3e436dc7653dfdf3da67332e22bff09bb0e28b0239e1624499c7830842e" +dependencies = [ + "windows-link", + "windows-result 0.3.2", + "windows-strings 0.4.0", +] + [[package]] name = "windows-result" version = "0.1.2" @@ -6436,6 +7300,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-strings" version = "0.1.0" @@ -6446,6 +7319,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -6678,6 +7560,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb8234a863ea0e8cd7284fcdd4f145233eb00fee02bbdd9861aec44e6477bc5" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.52.0" @@ -6698,13 +7589,33 @@ dependencies = [ ] [[package]] -name = "wry" -version = "0.46.2" +name = "wl-clipboard-rs" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa1c8c760041c64ce6be99f83d6cb55fe3fcd85a1ad46d32895f6e65cee87ba" +checksum = "2a083daad7e8a4b8805ad73947ccadabe62afe37ce0e9787a56ff373d34762c7" +dependencies = [ + "libc", + "log", + "os_pipe", + "rustix 0.38.44", + "tempfile", + "thiserror 1.0.64", + "tree_magic_mini", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-wlr", +] + +[[package]] +name = "wry" +version = "0.51.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c886a0a9d2a94fd90cfa1d929629b79cfefb1546e2c7430c63a47f0664c0e4e2" dependencies = [ "base64 0.22.1", - "block2", + "block2 0.6.1", + "cookie", "crossbeam-channel", "dpi", "dunce", @@ -6717,9 +7628,10 @@ dependencies = [ "kuchikiki", "libc", "ndk", - "objc2", + "objc2 0.6.1", "objc2-app-kit", - "objc2-foundation", + "objc2-core-foundation", + "objc2-foundation 0.3.1", "objc2-ui-kit", "objc2-web-kit", "once_cell", @@ -6728,13 +7640,14 @@ dependencies = [ "sha2", "soup3", "tao-macros", - "thiserror 1.0.64", + "thiserror 2.0.12", "tracing", + "url", "webkit2gtk", "webkit2gtk-sys", "webview2-com", - "windows 0.58.0", - "windows-core 0.58.0", + "windows 0.61.1", + "windows-core 0.61.0", "windows-version", "x11-dl", ] @@ -6770,12 +7683,40 @@ dependencies = [ ] [[package]] -name = "xz2" -version = "0.1.7" +name = "x11rb" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" dependencies = [ - "lzma-sys", + "gethostname", + "rustix 0.38.44", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" + +[[package]] +name = "xattr" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e" +dependencies = [ + "libc", + "rustix 1.0.3", +] + +[[package]] +name = "xdg-home" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +dependencies = [ + "libc", + "windows-sys 0.59.0", ] [[package]] @@ -6784,6 +7725,70 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "zbus" +version = "5.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59c333f648ea1b647bc95dc1d34807c8e25ed7a6feff3394034dc4776054b236" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "nix", + "ordered-stream", + "serde", + "serde_repr", + "static_assertions", + "tokio", + "tracing", + "uds_windows", + "windows-sys 0.59.0", + "winnow 0.7.7", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f325ad10eb0d0a3eb060203494c3b7ec3162a01a59db75d2deee100339709fc0" +dependencies = [ + "proc-macro-crate 3.3.0", + "proc-macro2", + "quote", + "syn 2.0.101", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" +dependencies = [ + "serde", + "static_assertions", + "winnow 0.7.7", + "zvariant", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -6802,7 +7807,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -6810,20 +7815,6 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] [[package]] name = "zip" @@ -6831,41 +7822,13 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc5e4288ea4057ae23afc69a4472434a87a2495cafce6632fd1c4ec9f5cf3494" dependencies = [ - "aes", "arbitrary", - "bzip2", - "constant_time_eq", "crc32fast", "crossbeam-utils", - "deflate64", "displaydoc", - "flate2", - "hmac", "indexmap 2.5.0", - "lzma-rs", "memchr", - "pbkdf2", - "rand 0.8.5", - "sha1", "thiserror 1.0.64", - "time", - "zeroize", - "zopfli", - "zstd", -] - -[[package]] -name = "zopfli" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" -dependencies = [ - "bumpalo", - "crc32fast", - "lockfree-object-pool", - "log", - "once_cell", - "simd-adler32", ] [[package]] @@ -6895,3 +7858,46 @@ dependencies = [ "cc", "pkg-config", ] + +[[package]] +name = "zvariant" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2df9ee044893fcffbdc25de30546edef3e32341466811ca18421e3cd6c5a3ac" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "url", + "winnow 0.7.7", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74170caa85b8b84cc4935f2d56a57c7a15ea6185ccdd7eadb57e6edd90f94b2f" +dependencies = [ + "proc-macro-crate 3.3.0", + "proc-macro2", + "quote", + "syn 2.0.101", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16edfee43e5d7b553b77872d99bc36afdda75c223ca7ad5e3fbecd82ca5fc34" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "static_assertions", + "syn 2.0.101", + "winnow 0.7.7", +] diff --git a/Cargo.toml b/Cargo.toml index 48eadd5c..d96d4b30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "apps/desktop", "packages/core", "packages/entity", "packages/gamemode", @@ -10,7 +11,10 @@ members = [ resolver = "2" # virtual workspace resolver so we don't need a root package [workspace.package] -authors = [ "Polyfrost", "Polyfrost Contributors" ] +authors = [ + "Polyfrost", + "Polyfrost Contributors" +] documentation = "https://docs.polyfrost.org/onelauncher" edition = "2024" homepage = "https://polyfrost.org/projects/onelauncher" @@ -28,67 +32,74 @@ onelauncher_macro = { path = "./packages/macro" } onelauncher_migration = { path = "./packages/migration" } serde_json_path_to_error = { path = "./packages/serde_json_path_to_error" } +# core +interpulse = { version = "=1.2.2", features = [ "specta" ] } + # tauri -tauri = { version = "2.0", features = [ +tauri = { version = "=2.5.1", features = [ "rustls-tls", "tracing", "specta", "uuid", "protocol-asset", ] } -tauri-build = { version = "2.0" } -tauri-plugin-clipboard-manager = { version = "2.0" } -tauri-plugin-deep-link = { version = "2.0" } -tauri-plugin-dialog = { version = "2.0" } -tauri-plugin-shell = { version = "2.0" } -tauri-plugin-single-instance = { version = "2.0" } -tauri-plugin-updater = { version = "2.0" } -tauri-plugin-window-state = { version = "2.0" } - -# core -interpulse = { version = "=1.2.2", features = [ "specta" ] } +tauri-build = { version = "=2.2.0" } +tauri-plugin-clipboard-manager = { version = "=2.2.2" } +tauri-plugin-deep-link = { version = "=2.2.1" } +tauri-plugin-dialog = { version = "=2.2.1" } +tauri-plugin-single-instance = { version = "=2.2.3" } +tauri-plugin-updater = { version = "=2.7.1" } +tauri-plugin-window-state = { version = "=2.2.2" } # code gen -specta = { version = "2.0.0-rc.20", features = [ +strum = { version = "=0.27.1", features = [ "derive" ] } +specta = { version = "=2.0.0-rc.22", features = [ "chrono", "uuid", "serde", "serde_json", "function", "derive", - "time", + "tokio" ] } -specta-typescript = { version = "=0.0.7" } -tauri-specta = { version = "2.0.0-rc.20", features = [ "javascript", "typescript", "derive" ] } - -# security -iota_stronghold = { version = "2.1" } -zeroize = { version = "1.8", features = [ "zeroize_derive" ] } +specta-typescript = { version = "=0.0.9" } +tauri-specta = { version = "=2.0.0-rc.21", features = [ "derive" ] } # logging tracing = { version = "0.1" } tracing-appender = { version = "0.2" } tracing-error = { version = "0.2" } -tracing-subscriber = { version = "0.3", features = [ "chrono", "env-filter" ] } +tracing-subscriber = { version = "0.3", features = [ + "chrono", + "env-filter" +] } # serialization & text -base64 = { version = "0.22" } -byteorder = { version = "1.5.0" } +base64 = { version = "=0.22.1" } +byteorder = { version = "=1.5.0" } chrono = { version = "0.4", features = [ "serde" ] } -nom = { version = "7.1" } -p256 = { version = "0.13.2", features = [ "ecdsa" ] } -rand = { version = "0.8.5" } -regex = { version = "1.10" } +nom = { version = "=8.0.0" } +rand = { version = "0.8" } +regex = { version = "1.11" } serde = { version = "1.0", features = [ "derive" ] } -serde_json = { version = "1.0" } -serde_with = { version = "=3.12.0", features = [ "chrono_0_4", "json", "macros" ] } -strum = { version = "0.26", features = [ "derive" ] } +serde_json = { version = "=1.0" } +serde_with = { version = "=3.12.0", features = [ + "chrono_0_4", + "json", + "macros" +] } toml = { version = "0.8" } -url = { version = "2.5.2" } -uuid = { version = "1.10", features = [ "serde", "v4" ] } +url = { version = "2.5" } +uuid = { version = "1.16", features = [ + "serde", + "v4" +] } # network -async-tungstenite = { version = "0.28", features = [ "tokio-runtime", "tokio-rustls-webpki-roots" ] } +# async-tungstenite = { version = "0.28", features = [ +# "tokio-runtime", +# "tokio-rustls-webpki-roots" +# ] } reqwest = { version = "0.12", default-features = false, features = [ "json", "stream", @@ -102,30 +113,31 @@ reqwest = { version = "0.12", default-features = false, features = [ "gzip", "brotli", ] } -webbrowser = { version = "1.0" } +webbrowser = { version = "=1.0.4" } # io -async-compression = { version = "0.4.12", features = [ "flate2", "tokio" ] } -async_zip = { version = "0.0.17", features = [ "full" ] } -bytes = { version = "1.7" } -dirs = { version = "5.0" } -dunce = { version = "1.0.5" } -flate2 = { version = "1.0" } -notify = { version = "6.1", default-features = false } -notify-debouncer-mini = { version = "0.4", default-features = false } -sanitize-filename = { version = "0.6" } -tempfile = { version = "3.19" } -tokio = { version = "1.40", features = [ "full" ] } -tokio-stream = { version = "0.1.16", features = [ "fs" ] } +async-compression = { version = "=0.4.23", features = [ + "flate2", + "tokio" +] } +async_zip = { version = "=0.0.17", features = [ "full" ] } +bytes = { version = "=1.10.1" } +dirs = { version = "=6.0.0" } +dunce = { version = "=1.0.5" } +flate2 = { version = "=1.1.1" } +sanitize-filename = { version = "=0.6.0" } +tempfile = { version = "=3.19.1" } +tokio = { version = "1", features = [ "full" ] } +tokio-stream = { version = "0.1", features = [ "fs" ] } tokio-util = { version = "0.7", features = [ "compat" ] } -zip = { version = "2.2.0" } # crypto digest = { version = "0.10" } md-5 = { version = "0.10" } -murmur2 = { version = "0.1.0" } +murmur2 = { version = "=0.1.0" } sha1 = { version = "0.10" } sha2 = { version = "0.10" } +p256 = { version = "0.13", features = [ "ecdsa" ] } # db sea-orm = { version = "1.1", features = [ @@ -145,23 +157,29 @@ sea-orm-migration = { version = "1.1", features = [ ] } # utils -anyhow = { version = "1.0" } -async-trait = { version = "0.1" } -cc = { version = "1.1" } -discord-rich-presence = { version = "0.2" } +anyhow = { version = "1" } +async-trait = { version = "=0.1.88" } +cc = { version = "1" } +discord-rich-presence = { version = "=0.2.5" } futures = { version = "0.3" } futures-lite = { version = "2.6" } indicatif = { version = "0.17" } libc = { version = "0.2" } -merge = { version = "0.2" } -sysinfo = { version = "0.31", features = [ "serde" ] } -thiserror = { version = "1.0" } -whoami = { version = "1.5" } +merge = { version = "=0.2.0" } +sysinfo = { version = "=0.34.2", features = [ "serde" ] } +thiserror = { version = "2" } +whoami = { version = "=1.6.0" } # macro paste = { version = "1" } quote = { version = "1" } syn = { version = "2" } +proc-macro2 = { version = "1" } + +[patch.crates-io] +# tauri-specta = { git = "https://github.com/specta-rs/tauri-specta", rev = "6fe9532a44b8b683b4dacd178b0120ee585bea23" } +specta = { git = "https://github.com/specta-rs/specta", rev = "30661263250e7b6f9b36cdc56e6c843ac5abb226" } +specta-typescript = { git = "https://github.com/specta-rs/specta", rev = "30661263250e7b6f9b36cdc56e6c843ac5abb226" } [workspace.lints.clippy] all = { level = "warn", priority = -1 } @@ -186,17 +204,6 @@ style = { level = "warn", priority = -1 } suspicious = { level = "warn", priority = -1 } unnecessary_cast = "warn" -[workspace.lints.rust] -deprecated = "warn" -deprecated_in_future = "forbid" -rust_2018_idioms = { level = "warn", priority = -1 } -trivial_casts = "warn" -trivial_numeric_casts = "warn" -unused_allocation = "warn" -unused_import_braces = "warn" -unused_must_use = "deny" -unused_qualifications = "warn" - [profile.dev] # add debug symbols/logic and optimize for reduced build time (parallel) codegen-units = 256 # make parallel incremental builds faster diff --git a/apps/desktop/Cargo.toml b/apps/desktop/Cargo.toml index db4ddc0a..42d59941 100644 --- a/apps/desktop/Cargo.toml +++ b/apps/desktop/Cargo.toml @@ -13,7 +13,11 @@ authors = { workspace = true } [lib] name = "onelauncher_gui" -crate-type = [ "staticlib", "cdylib", "lib" ] +crate-type = [ + "staticlib", + "cdylib", + "lib" +] [lints] workspace = true @@ -22,31 +26,40 @@ workspace = true tauri-build = { workspace = true } [dependencies] -onelauncher = { workspace = true, features = [ "tauri", "specta" ] } -onelauncher_macros = { workspace = true } -tauri-plugin-shell = { workspace = true } +# core +onelauncher_core = { workspace = true, features = [ "tauri" ] } +interpulse = { workspace = true } + +# tauri tauri-plugin-window-state = { workspace = true } tauri-plugin-single-instance = { workspace = true } tauri-plugin-updater = { workspace = true } tauri-plugin-clipboard-manager = { workspace = true } tauri-plugin-dialog = { workspace = true } tauri-plugin-deep-link = { workspace = true } + +# code gen tauri-specta = { workspace = true } specta = { workspace = true } specta-typescript = { workspace = true } -interpulse = { workspace = true } strum = { workspace = true } -once_cell = { workspace = true } -tokio = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -thiserror = { workspace = true } -anyhow = { workspace = true } -chrono = { workspace = true } -uuid = { workspace = true } + +# logging tracing = { workspace = true } tracing-error = { workspace = true } -mimalloc = { workspace = true } + +# serialization & text +serde = { workspace = true } +serde_json = { workspace = true } +chrono = { workspace = true } +uuid = { workspace = true } + +# io +tokio = { workspace = true } + +# utils +thiserror = { workspace = true } +anyhow = { workspace = true } [dependencies.tauri] workspace = true @@ -58,18 +71,10 @@ features = [ "specta" ] -[dev-dependencies.tauri] -workspace = true -features = [ "test" ] - -[target.'cfg(target_os = "macos")'.dependencies] -cocoa = { workspace = true } -objc = { workspace = true } - -[target.'cfg(target_os = "linux")'.dependencies] -webkit2gtk = { workspace = true } - [features] -default = [ "custom-protocol", "devtools" ] +default = [ + "custom-protocol", + "devtools" +] devtools = [ "tauri/devtools" ] custom-protocol = [ "tauri/custom-protocol" ] diff --git a/apps/desktop/capabilities/default.json b/apps/desktop/capabilities/default.json index 73cd4f32..40d2165e 100644 --- a/apps/desktop/capabilities/default.json +++ b/apps/desktop/capabilities/default.json @@ -26,7 +26,6 @@ "core:webview:allow-internal-toggle-devtools", "core:event:default", - "shell:allow-open", "clipboard-manager:allow-write-text", "dialog:allow-open", "dialog:allow-save", diff --git a/apps/desktop/tauri.conf.json b/apps/desktop/tauri.conf.json index 7b53ec56..69a350e7 100644 --- a/apps/desktop/tauri.conf.json +++ b/apps/desktop/tauri.conf.json @@ -13,8 +13,7 @@ "allow": [ "$DATA/OneLauncher/**", "$RESOURCE/**" - ], - "deny": ["$DATA/OneLauncher/db/*.stronghold"] + ] } }, "freezePrototype": true, @@ -79,7 +78,7 @@ "desktopTemplate": "../../packages/distribution/linux/org.polyfrost.launcher.desktop.template" }, "appimage": { - + } }, "shortDescription": "OneLauncher", diff --git a/eslint.config.ts b/eslint.config.ts index e34a27c9..295489c7 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -8,4 +8,10 @@ export default defineConfig({ rules: { 'ts/no-use-before-define': 'off', }, + toml: { + overrides: { + 'toml/array-element-newline': ['error', 'always'], + 'toml/no-mixed-type-in-array': ['off'], + }, + }, }); diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index d2f43e1a..74a7564a 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -14,14 +14,18 @@ authors = { workspace = true } path = "src/lib.rs" [features] -default = [ "cli" ] +default = [ "tauri" ] tauri = [ "dep:tauri", "dep:tauri-specta", "specta", "gui" ] -specta = [ "dep:specta", "interpulse/specta", "onelauncher_entity/specta" ] +specta = [ + "dep:specta", + "interpulse/specta", + "onelauncher_entity/specta" +] cli = [ "dep:indicatif" ] gui = [] @@ -80,7 +84,6 @@ md-5 = { workspace = true } p256 = { workspace = true } # io -zip = { workspace = true } async_zip = { workspace = true } dirs = { workspace = true } bytes = { workspace = true } diff --git a/packages/core/examples/dirs.rs b/packages/core/examples/dirs.rs index 81ed53a4..b58ed72c 100644 --- a/packages/core/examples/dirs.rs +++ b/packages/core/examples/dirs.rs @@ -1,7 +1,8 @@ -use onelauncher_core::store::Dirs; +use onelauncher_core::store::{Core, CoreOptions, Dirs}; #[tokio::main] async fn main() { + Core::initialize(CoreOptions::default()).await.expect("couldn't initialize core"); let dirs = Dirs::get().await.expect("couldn't initialize dirs"); println!(" Base dir : {}", dirs.base_dir().display()); diff --git a/packages/core/package.json b/packages/core/package.json index 4f66b956..6544668f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -14,6 +14,7 @@ "cargo": "cargo", "binary": "cargo run --bin", "example": "cargo run --example", - "test": "cargo test" + "test": "cargo test", + "lint:rs": "cargo clippy --all-targets" } } diff --git a/packages/core/src/api/cluster/mod.rs b/packages/core/src/api/cluster/mod.rs index 024d33a8..681051c4 100644 --- a/packages/core/src/api/cluster/mod.rs +++ b/packages/core/src/api/cluster/mod.rs @@ -108,10 +108,6 @@ pub async fn create_cluster( /// Make a cluster playable (installs all necessary game files and dependencies required to play) pub async fn prepare_cluster(cluster: &mut clusters::Model, force: Option) -> LauncherResult<&clusters::Model> { - const INGRESS_TOTAL: f64 = 100.0; - const INGRESS_TASKS: f64 = 4.0; - const INGRESS_STEP: f64 = INGRESS_TOTAL / INGRESS_TASKS; - tracing::debug!("preparing cluster {}", cluster.name); // if cluster.stage == ClusterStage::Ready { @@ -119,14 +115,6 @@ pub async fn prepare_cluster(cluster: &mut clusters::Model, force: Option) // return Ok(cluster); // } - let ingress_id = init_ingress( - IngressType::PrepareCluster { - cluster_name: cluster.name.clone(), - }, - "preparing cluster", - INGRESS_TOTAL, - ) - .await?; dao::update_cluster(cluster, async |mut cluster| { cluster.stage = Set(ClusterStage::Downloading); @@ -134,62 +122,7 @@ pub async fn prepare_cluster(cluster: &mut clusters::Model, force: Option) }) .await?; - let result: LauncherResult<()> = { - // TASK 1 - ingress_id.set_ingress_message("fetching data").await?; - let setting_profile = - setting_profiles::dao::get_profile_or_default(cluster.setting_profile_name.as_ref()).await?; - - let state = State::get().await?; - let mut metadata = state.metadata.write().await; - - let version = metadata - .get_vanilla_or_fetch() - .await? - .versions - .iter() - .find(|v| v.id == cluster.mc_version) - .ok_or_else(|| ClusterError::InvalidVersion(cluster.mc_version.clone()))? - .clone(); - - drop(metadata); - - let loader_version = metadata::get_loader_version( - &cluster.mc_version, - cluster.mc_loader, - cluster.mc_loader_version.as_deref(), - ) - .await?; - - // TASK 2 - let mut version_info = metadata::download_version_info( - &version, - loader_version.as_ref(), - Some(&SubIngress::new(&ingress_id, INGRESS_STEP)), - force, - ) - .await?; - - let java_version = if let Some(version) = java::get_recommended_java(&version_info, Some(&setting_profile)).await? { - version - } else { - let Some(ver) = &version_info.java_version else { - return Err(ClusterError::MissingJavaVersion.into()); - }; - - java::prepare_java(ver.major_version).await? - }; - - // TASK 3 - download_minecraft(&version_info, &java_version.arch, Some(&SubIngress::new(&ingress_id, INGRESS_STEP)), force).await?; - - // TASK 4 - run_forge_processors(cluster, &mut version_info, java_version, &SubIngress::new(&ingress_id, INGRESS_STEP)).await?; - - Ok(()) - }; - - if let Err(err) = result { + if let Err(err) = install_cluster(cluster, force).await { tracing::error!("failed to prepare cluster: {}", err); dao::update_cluster(cluster, async |mut cluster| { cluster.stage = Set(ClusterStage::NotReady); @@ -209,6 +142,74 @@ pub async fn prepare_cluster(cluster: &mut clusters::Model, force: Option) Ok(cluster) } +async fn install_cluster(cluster: &clusters::Model, force: Option) -> LauncherResult<()> { + const INGRESS_TOTAL: f64 = 100.0; + const INGRESS_TASKS: f64 = 4.0; + const INGRESS_STEP: f64 = INGRESS_TOTAL / INGRESS_TASKS; + + let ingress_id = init_ingress( + IngressType::PrepareCluster { + cluster_name: cluster.name.clone(), + }, + "preparing cluster", + INGRESS_TOTAL, + ) + .await?; + + // TASK 1 + ingress_id.set_ingress_message("fetching data").await?; + let setting_profile = + setting_profiles::dao::get_profile_or_default(cluster.setting_profile_name.as_ref()).await?; + + let state = State::get().await?; + let mut metadata = state.metadata.write().await; + + let version = metadata + .get_vanilla_or_fetch() + .await? + .versions + .iter() + .find(|v| v.id == cluster.mc_version) + .ok_or_else(|| ClusterError::InvalidVersion(cluster.mc_version.clone()))? + .clone(); + + drop(metadata); + + let loader_version = metadata::get_loader_version( + &cluster.mc_version, + cluster.mc_loader, + cluster.mc_loader_version.as_deref(), + ) + .await?; + + // TASK 2 + let mut version_info = metadata::download_version_info( + &version, + loader_version.as_ref(), + Some(&SubIngress::new(&ingress_id, INGRESS_STEP)), + force, + ) + .await?; + + let java_version = if let Some(version) = java::get_recommended_java(&version_info, Some(&setting_profile)).await? { + version + } else { + let Some(ver) = &version_info.java_version else { + return Err(ClusterError::MissingJavaVersion.into()); + }; + + java::prepare_java(ver.major_version).await? + }; + + // TASK 3 + download_minecraft(&version_info, &java_version.arch, Some(&SubIngress::new(&ingress_id, INGRESS_STEP)), force).await?; + + // TASK 4 + run_forge_processors(cluster, &mut version_info, java_version, &SubIngress::new(&ingress_id, INGRESS_STEP)).await?; + + Ok(()) +} + /// Run forge processors async fn run_forge_processors( cluster: &clusters::Model, @@ -265,11 +266,10 @@ async fn run_forge_processors( ingress.set_ingress_message("running forge processors").await?; let total_length = processors.len(); for (index, processor) in processors.iter().enumerate() { - if let Some(sides) = &processor.sides { - if !sides.contains(&String::from("client")) { + if let Some(sides) = &processor.sides + && !sides.contains(&String::from("client")) { continue; } - } let mut cp = processor.classpath.clone(); cp.push(processor.jar.clone()); diff --git a/packages/core/src/api/game/arguments.rs b/packages/core/src/api/game/arguments.rs index 8122eed7..c3c30264 100644 --- a/packages/core/src/api/game/arguments.rs +++ b/packages/core/src/api/game/arguments.rs @@ -179,12 +179,11 @@ pub async fn main_class(path: String) -> LauncherResult> { for line in buf.lines() { let line = line.trim(); - if line.starts_with("Main-Class:") { - if let Some(class) = line.split(':').nth(1) { + if line.starts_with("Main-Class:") + && let Some(class) = line.split(':').nth(1) { class_name = Some(class.to_string()); return Ok(()); } - } } Ok(()) @@ -272,11 +271,10 @@ pub fn classpaths( let mut classpaths = libraries .iter() .filter_map(|lib| { - if let Some(rules) = &lib.rules { - if !super::rules::validate_rules(rules, java_arch, updated) { + if let Some(rules) = &lib.rules + && !super::rules::validate_rules(rules, java_arch, updated) { return None; } - } if !lib.include_in_classpath { return None; diff --git a/packages/core/src/api/game/launch.rs b/packages/core/src/api/game/launch.rs index 7a78f59b..7aa07f7f 100644 --- a/packages/core/src/api/game/launch.rs +++ b/packages/core/src/api/game/launch.rs @@ -32,11 +32,10 @@ pub async fn launch_minecraft( prepare_cluster(cluster, force).await?; let mut settings = get_global_profile().await; - if let Some(name) = &cluster.setting_profile_name { - if let Some(profile) = get_profile_by_name(name).await? { + if let Some(name) = &cluster.setting_profile_name + && let Some(profile) = get_profile_by_name(name).await? { settings.merge(profile); } - } let state = State::get().await?; let dirs = Dirs::get().await?; diff --git a/packages/core/src/api/game/metadata.rs b/packages/core/src/api/game/metadata.rs index f869e7ee..5dc95cdf 100644 --- a/packages/core/src/api/game/metadata.rs +++ b/packages/core/src/api/game/metadata.rs @@ -285,12 +285,11 @@ pub async fn download_libraries( ingress.set_ingress_message("fetching libraries").await?; let requests = stream::iter(libraries.iter().map(|lib| async { - if let Some(rules) = &lib.rules { - if !super::rules::validate_rules(rules, java_arch, lib.natives.is_some()) { + if let Some(rules) = &lib.rules + && !super::rules::validate_rules(rules, java_arch, lib.natives.is_some()) { tracing::debug!("skipping library {} due to rules", lib.name); return Ok::<(), LauncherError>(()); } - } if !lib.downloadable { tracing::debug!("skipping library {} due to downloadability", lib.name); @@ -309,8 +308,8 @@ pub async fn download_libraries( async { if let Some(interpulse::api::minecraft::LibraryDownloads { artifact: Some(ref artifact), .. - }) = lib.downloads { - if !artifact.url.is_empty() { + }) = lib.downloads + && !artifact.url.is_empty() { http::download( Method::GET, &artifact.url, @@ -322,7 +321,6 @@ pub async fn download_libraries( tracing::trace!("fetched library {} to path {:?}", &lib.name, &path); return Ok::<_, LauncherError>(()); } - } let url = [lib.url.as_deref().unwrap_or("https://libraries.minecraft.net/"), &artifact_path].concat(); http::download( diff --git a/packages/core/src/api/game/rules.rs b/packages/core/src/api/game/rules.rs index 73475f72..454a69b4 100644 --- a/packages/core/src/api/game/rules.rs +++ b/packages/core/src/api/game/rules.rs @@ -75,11 +75,10 @@ pub fn validate_os_rule(rule: &OsRule, java_arch: &str, updated: bool) -> bool { } } - if let Some(version) = &rule.version { - if let Ok(regex) = Regex::new(version.as_str()) { + if let Some(version) = &rule.version + && let Ok(regex) = Regex::new(version.as_str()) { rule_match &= regex.is_match(&sysinfo::System::os_version().unwrap_or_default()); } - } rule_match } \ No newline at end of file diff --git a/packages/core/src/api/ingress.rs b/packages/core/src/api/ingress.rs index 6f4061cf..83655452 100644 --- a/packages/core/src/api/ingress.rs +++ b/packages/core/src/api/ingress.rs @@ -5,7 +5,7 @@ use crate::LauncherResult; use crate::store::State; use crate::store::ingress::{IngressId, IngressType, SubIngress}; -#[onelauncher_macro::specta(with_event)] +#[onelauncher_macro::specta] #[derive(Serialize, Debug, Clone)] pub struct IngressPayload { pub id: Uuid, diff --git a/packages/core/src/api/java/mod.rs b/packages/core/src/api/java/mod.rs index b82f462b..1cf5bbdf 100644 --- a/packages/core/src/api/java/mod.rs +++ b/packages/core/src/api/java/mod.rs @@ -122,13 +122,10 @@ pub async fn get_recommended_java( // Settings profile is an override (it has highest priority) if let Some(profile) = profile && !profile.is_global() - { - if let Some(java_id) = profile.java_id { - if let Ok(Some(java)) = dao::get_java_by_id(java_id).await { + && let Some(java_id) = profile.java_id + && let Ok(Some(java)) = dao::get_java_by_id(java_id).await { return Ok(Some(java)); } - } - } // Check if the version info has a suggested version let Some(supported_ver) = &info.java_version else { diff --git a/packages/core/src/api/processes.rs b/packages/core/src/api/processes.rs index 70f38437..ef21db3c 100644 --- a/packages/core/src/api/processes.rs +++ b/packages/core/src/api/processes.rs @@ -2,7 +2,8 @@ use serde::Serialize; use crate::store::processes::Process; -#[derive(Debug, Serialize)] +#[onelauncher_macro::specta] +#[derive(Debug, Clone, Serialize)] pub enum ProcessPayload { Starting { command: String, diff --git a/packages/core/src/api/proxy/event.rs b/packages/core/src/api/proxy/event.rs index c513d2bc..6b729366 100644 --- a/packages/core/src/api/proxy/event.rs +++ b/packages/core/src/api/proxy/event.rs @@ -2,8 +2,8 @@ use serde::Serialize; use crate::{error::LauncherResult, store::proxy::ProxyState}; -#[onelauncher_macro::specta(with_event)] -#[derive(Debug, Serialize)] +#[onelauncher_macro::specta(event)] +#[derive(Debug, Clone, Serialize)] pub enum LauncherEvent { Ingress(crate::api::ingress::IngressPayload), Message(crate::api::proxy::message::MessagePayload), diff --git a/packages/core/src/api/proxy/impl/mod.rs b/packages/core/src/api/proxy/impl/mod.rs index e14d69a2..73e243a0 100644 --- a/packages/core/src/api/proxy/impl/mod.rs +++ b/packages/core/src/api/proxy/impl/mod.rs @@ -1,6 +1,6 @@ -#[cfg(feature = "tauri")] mod proxy_tauri; -#[cfg(feature = "cli")] mod proxy_cli; -mod proxy_empty; +#[cfg(feature = "tauri")] pub mod proxy_tauri; +#[cfg(feature = "cli")] pub mod proxy_cli; +pub mod proxy_empty; #[cfg(feature = "tauri")] pub use proxy_tauri::ProxyTauri; #[cfg(feature = "cli")] pub use proxy_cli::ProxyCli; diff --git a/packages/core/src/api/proxy/impl/proxy_empty.rs b/packages/core/src/api/proxy/impl/proxy_empty.rs index 18cccb26..d4b0e5af 100644 --- a/packages/core/src/api/proxy/impl/proxy_empty.rs +++ b/packages/core/src/api/proxy/impl/proxy_empty.rs @@ -34,13 +34,13 @@ impl LauncherProxy for ProxyEmpty { } #[cfg(feature = "gui")] - fn hide_main_window(&self) -> crate::LauncherResult<()> { + fn hide_main_window(&self) -> LauncherResult<()> { println!("hidden window"); Ok(()) } #[cfg(feature = "gui")] - fn show_main_window(&self) -> crate::LauncherResult<()> { + fn show_main_window(&self) -> LauncherResult<()> { println!("shown window"); Ok(()) } diff --git a/packages/core/src/api/proxy/impl/proxy_tauri.rs b/packages/core/src/api/proxy/impl/proxy_tauri.rs index 282016be..62417f95 100644 --- a/packages/core/src/api/proxy/impl/proxy_tauri.rs +++ b/packages/core/src/api/proxy/impl/proxy_tauri.rs @@ -2,7 +2,7 @@ use tauri::{AppHandle, Emitter, Manager}; use tauri_specta::Event; use tracing::{error, warn}; -use crate::{constants::CLI_TOTAL_INGRESS, api::proxy::{event::LauncherEvent, LauncherProxy, message::MessageLevel}, LauncherResult}; +use crate::{api::proxy::{event::LauncherEvent, LauncherProxy, message::MessageLevel}, LauncherResult}; #[derive(Debug)] pub struct ProxyTauri { @@ -21,19 +21,16 @@ impl ProxyTauri { #[async_trait::async_trait] impl LauncherProxy for ProxyTauri { async fn send_event(&self, event: LauncherEvent) -> LauncherResult<()> { - match event { - LauncherEvent::Message(message) => { - match message.level { - MessageLevel::Info => {}, - MessageLevel::Warning => warn!("{}", message.message), - MessageLevel::Error => error!("{}", message.message), - }; - }, - _ => {} + if let LauncherEvent::Message(message) = &event { + match message.level { + MessageLevel::Info => {}, + MessageLevel::Warn => warn!("{}", message.message), + MessageLevel::Error => error!("{}", message.message), + } } self.handle - .emit_all(LauncherEvent::NAME, event) + .emit(LauncherEvent::NAME, event) .map_err(Into::into) } diff --git a/packages/core/src/api/proxy/message.rs b/packages/core/src/api/proxy/message.rs index 2a63122c..2bc8585c 100644 --- a/packages/core/src/api/proxy/message.rs +++ b/packages/core/src/api/proxy/message.rs @@ -2,7 +2,7 @@ use serde::Serialize; use super::event::send_event; -#[onelauncher_macro::specta(with_event)] +#[onelauncher_macro::specta] #[derive(Serialize, Debug, Clone)] pub struct MessagePayload { pub level: MessageLevel, diff --git a/packages/core/src/api/setting_profiles.rs b/packages/core/src/api/setting_profiles.rs index 71dd6239..d8611c86 100644 --- a/packages/core/src/api/setting_profiles.rs +++ b/packages/core/src/api/setting_profiles.rs @@ -1,24 +1,26 @@ use onelauncher_entity::setting_profiles; use sea_orm::ActiveValue::Set; -use crate::{error::LauncherResult, store::State}; +use crate::error::LauncherResult; +use crate::store::State; pub mod dao { use onelauncher_entity::setting_profiles; use sea_orm::{ActiveModelTrait, EntityTrait}; - use crate::{error::{DaoError, LauncherResult}, store::State}; + use crate::error::{DaoError, LauncherResult}; + use crate::store::State; pub async fn get_all_profiles() -> LauncherResult> { let state = State::get().await?; let db = &state.db; - Ok(setting_profiles::Entity::find() - .all(db) - .await?) + Ok(setting_profiles::Entity::find().all(db).await?) } - pub async fn get_profile_or_default(name: Option<&String>) -> LauncherResult { + pub async fn get_profile_or_default( + name: Option<&String>, + ) -> LauncherResult { if let Some(name) = name { let profile = get_profile_by_name(name).await?; if let Some(profile) = profile { @@ -31,20 +33,22 @@ pub mod dao { Ok(settings.global_game_settings.clone()) } - pub async fn get_profile_by_name(name: &str) -> LauncherResult> { + pub async fn get_profile_by_name( + name: &str, + ) -> LauncherResult> { let state = State::get().await?; let db = &state.db; - Ok(setting_profiles::Entity::find_by_id(name) - .one(db) - .await?) + Ok(setting_profiles::Entity::find_by_id(name).one(db).await?) } - pub async fn insert_profile(profile: setting_profiles::ActiveModel) -> LauncherResult { - if let Some(name) = profile.name.try_as_ref() { - if name.is_empty() || profile.is_global() { - return Err(DaoError::InvalidValue(name.to_string(), "name".into()).into()); - } + pub async fn insert_profile( + profile: setting_profiles::ActiveModel, + ) -> LauncherResult { + if let Some(name) = profile.name.try_as_ref() + && (name.is_empty() || profile.is_global()) + { + return Err(DaoError::InvalidValue(name.to_string(), "name".into()).into()); } let state = State::get().await?; @@ -57,7 +61,9 @@ pub mod dao { let state = State::get().await?; let db = &state.db; - let deleted = setting_profiles::Entity::delete_by_id(name).exec(db).await?; + let deleted = setting_profiles::Entity::delete_by_id(name) + .exec(db) + .await?; if deleted.rows_affected == 0 { return Err(DaoError::NotFound.into()); @@ -78,7 +84,9 @@ pub async fn get_global_profile() -> setting_profiles::Model { /// Creates a new setting profile and inserts it into the database. Returns the inserted entry pub async fn create_profile(name: &str, block: F) -> LauncherResult -where F: AsyncFnOnce(setting_profiles::ActiveModel) -> LauncherResult { +where + F: AsyncFnOnce(setting_profiles::ActiveModel) -> LauncherResult, +{ let model = setting_profiles::ActiveModel { name: Set(name.to_string()), ..Default::default() diff --git a/packages/core/src/store/credentials.rs b/packages/core/src/store/credentials.rs index f07939ab..c10006fd 100644 --- a/packages/core/src/store/credentials.rs +++ b/packages/core/src/store/credentials.rs @@ -365,8 +365,8 @@ pub struct SaveDeviceToken { } /// Core variables passed throughout the Minecraft login flow. +#[onelauncher_macro::specta] #[derive(Serialize, Deserialize, Debug)] -#[cfg_attr(feature = "specta", derive(specta::Type))] pub struct MinecraftLogin { pub verify: String, /// The OAuth challenge generated at the start of the flow. @@ -378,8 +378,8 @@ pub struct MinecraftLogin { } /// A structure of all needed Minecraft credentials for logging in and account management. +#[onelauncher_macro::specta] #[derive(Serialize, Deserialize, Debug, Clone)] -#[cfg_attr(feature = "specta", derive(specta::Type))] pub struct MinecraftCredentials { /// The uuid of the credentials. pub id: Uuid, diff --git a/packages/core/src/store/processes.rs b/packages/core/src/store/processes.rs index baf7974c..0faf077d 100644 --- a/packages/core/src/store/processes.rs +++ b/packages/core/src/store/processes.rs @@ -14,15 +14,15 @@ use uuid::Uuid; use crate::api::cluster; use crate::api::cluster::dao::ClusterId; -use crate::api::game::log::{censor_line, CensorMap}; +use crate::api::game::log::{CensorMap, censor_line}; use crate::api::processes::ProcessPayload; -use crate::api::proxy::event::{send_event, LauncherEvent}; +use crate::api::proxy::event::{LauncherEvent, send_event}; use crate::error::LauncherResult; use crate::send_error; use crate::utils::io::IOError; -use super::credentials::MinecraftCredentials; use super::State; +use super::credentials::MinecraftCredentials; #[derive(Debug, thiserror::Error)] pub enum ProcessError { @@ -37,7 +37,8 @@ pub struct ProcessStore { processes: RwLock>>>, } -#[derive(Debug, Serialize)] +#[onelauncher_macro::specta] +#[derive(Debug, Clone, Serialize)] pub struct Process { pub pid: u32, pub started_at: DateTime, @@ -47,7 +48,7 @@ pub struct Process { #[serde(skip)] pub child_type: Arc>, #[serde(skip)] - process_loop: Option>>, + process_loop: Option>>>, } #[derive(Debug, Serialize)] @@ -112,10 +113,7 @@ impl ProcessStore { } /// Returns a list of all running processes for a given cluster. - pub async fn get_running_by_cluster( - &self, - cluster_id: ClusterId, - ) -> Vec>> { + pub async fn get_running_by_cluster(&self, cluster_id: ClusterId) -> Vec>> { let mut list = Vec::new(); let read_guard = self.processes.read().await; @@ -129,10 +127,7 @@ impl ProcessStore { } /// Returns a list of all running processes for a given account. - pub async fn get_running_by_account( - &self, - account_id: Uuid, - ) -> Vec>> { + pub async fn get_running_by_account(&self, account_id: Uuid) -> Vec>> { let mut list = Vec::new(); let read_guard = self.processes.read().await; @@ -180,16 +175,18 @@ impl ProcessStore { settings: &setting_profiles::Model, mut command: Command, ) -> LauncherResult>> { - send_event(LauncherEvent::Process(ProcessPayload::Starting { command: format!("{command:?}") })).await; + send_event(LauncherEvent::Process(ProcessPayload::Starting { + command: format!("{command:?}"), + })) + .await; - if let Some(pre_hook) = &settings.hook_pre { - if let Err(err) = run_hook(pre_hook, cwd.clone()).await { - send_error!("{err:?}"); - } + if let Some(pre_hook) = &settings.hook_pre + && let Err(err) = run_hook(pre_hook, cwd.clone()).await + { + send_error!("{err:?}"); } - command - .stdout(Stdio::piped()) + command.stdout(Stdio::piped()) .stderr(Stdio::piped()) .stdin(Stdio::null()); @@ -205,10 +202,13 @@ impl ProcessStore { #[cfg(target_os = "linux")] { - if settings.os_extra.as_ref().is_some_and(|s| s.enable_gamemode.is_some_and(|x| x)) { - if let Err(err) = onelauncher_gamemode::request_start_for_wrapper(pid) { - tracing::warn!("failed to enable gamemode, continuing: {}", err); - } + if settings + .os_extra + .as_ref() + .is_some_and(|s| s.enable_gamemode.is_some_and(|x| x)) + && let Err(err) = onelauncher_gamemode::request_start_for_wrapper(pid) + { + tracing::warn!("failed to enable gamemode, continuing: {}", err); } } @@ -225,7 +225,8 @@ impl ProcessStore { send_event(LauncherEvent::Process(ProcessPayload::Output { pid, output: line, - })).await; + })) + .await; } }); @@ -237,9 +238,12 @@ impl ProcessStore { post_hook: settings.hook_post.clone(), account_id: creds.id, child_type: child_type.clone(), - process_loop: Some(tokio::task::spawn(Self::process_loop( - cluster_id, cwd, settings.hook_post.clone(), child_type, - ))), + process_loop: Some(Arc::new(tokio::task::spawn(Self::process_loop( + cluster_id, + cwd, + settings.hook_post.clone(), + child_type, + )))), }; let process = Arc::new(RwLock::new(process)); @@ -262,27 +266,8 @@ impl ProcessStore { child: Arc>, ) -> LauncherResult { let pid = child.read().await.id(); - let mut last_updated = Utc::now(); - let exit_code = loop { - let mut child = child.write().await; - if let Some(status) = child.try_wait()? { - tracing::info!("process exited with status: {:?}", status); - break status; - } - - // Every 60 seconds, update the playtime of the process - let duration = Utc::now().signed_duration_since(last_updated).num_seconds(); - if duration > 60 { - last_updated = Utc::now(); - - if let Err(err) = cluster::update_playtime(cluster_id, duration).await { - tracing::error!("failed to update playtime: {}", err); - } - } - - tokio::time::sleep(std::time::Duration::from_millis(100)).await; - }; + let exit_code = wait_for_exit(cluster_id, &child).await?; let state = State::get().await?; let store = &state.processes; @@ -292,13 +277,17 @@ impl ProcessStore { write_guard.remove(&pid); tracing::debug!("removed process with pid {}", pid); - if let Some(post_hook) = post_hook { - if let Err(err) = run_hook(&post_hook, cwd).await { - send_error!("{err:?}"); - } + if let Some(post_hook) = post_hook + && let Err(err) = run_hook(&post_hook, cwd).await + { + send_error!("{err:?}"); } - send_event(LauncherEvent::Process(ProcessPayload::Stopped { pid, exit_code })).await; + send_event(LauncherEvent::Process(ProcessPayload::Stopped { + pid, + exit_code, + })) + .await; Ok(exit_code) } @@ -332,4 +321,28 @@ async fn run_hook(hook: &str, cwd: PathBuf) -> LauncherResult> { } else { Err(ProcessError::HookUnsuccessful(exit_code).into()) } -} \ No newline at end of file +} + +async fn wait_for_exit(cluster_id: ClusterId, child: &Arc>) -> LauncherResult { + let mut last_updated = Utc::now(); + + loop { + let mut child = child.write().await; + if let Some(status) = child.try_wait()? { + tracing::info!("process exited with status: {:?}", status); + return Ok(status); + } + + // Every 60 seconds, update the playtime of the process + let duration = Utc::now().signed_duration_since(last_updated).num_seconds(); + if duration > 60 { + last_updated = Utc::now(); + + if let Err(err) = cluster::update_playtime(cluster_id, duration).await { + tracing::error!("failed to update playtime: {}", err); + } + } + + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + } +} diff --git a/packages/core/src/utils/io.rs b/packages/core/src/utils/io.rs index e6e80e00..fc157c8e 100644 --- a/packages/core/src/utils/io.rs +++ b/packages/core/src/utils/io.rs @@ -23,8 +23,6 @@ pub enum IOError { path: String, }, #[error(transparent)] - ZipError(#[from] zip::result::ZipError), - #[error(transparent)] IOError(#[from] std::io::Error), #[error("json deserialization error: {0}")] DeserializeError(#[from] serde_json::Error), @@ -295,11 +293,10 @@ pub async fn unzip_bytes_filtered( read_zip_entries_bytes(data, async |_, entry, entry_reader| { let file_name = entry.filename().as_str()?; - if let Some(filter) = &filter_entries { - if !filter(file_name) { + if let Some(filter) = &filter_entries + && !filter(file_name) { return Ok(()); } - } let path = dest_path.as_ref().join(sanitize_path(file_name)); diff --git a/packages/core_old/Cargo.toml b/packages/core_old/Cargo.toml deleted file mode 100644 index 64bc1edb..00000000 --- a/packages/core_old/Cargo.toml +++ /dev/null @@ -1,92 +0,0 @@ -[package] -name = "onelauncher" -description = "The core of OneLauncher" -version = { workspace = true } -license = { workspace = true } -edition = { workspace = true } -repository = { workspace = true } -documentation = { workspace = true } -readme = { workspace = true } -homepage = { workspace = true } -authors = { workspace = true } - -[features] -default = [] -# feature flag allowing us to interact with tauri (for loading animations, errors showing on gui, etc) -# also allows us to compile onelauncher core without tauri -tauri = [ "dep:tauri", "dep:tauri-specta", "specta" ] -specta = [ "dep:specta" ] -cli = [ "dep:indicatif" ] - -[lints] -workspace = true - -[dependencies] -# features[default] -onelauncher_macros = { workspace = true } -onelauncher_utils = { workspace = true } -interpulse = { workspace = true } -tracing = { workspace = true } -tracing-subscriber = { workspace = true } -tracing-error = { workspace = true } -tracing-appender = { workspace = true } -notify = { workspace = true } -notify-debouncer-mini = { workspace = true } -iota_stronghold = { workspace = true } -zeroize = { workspace = true } -tokio = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -serde_ini = { workspace = true } -serde_repr = { workspace = true } -serde_with = { workspace = true } -paste = { workspace = true } -futures = { workspace = true } -zip = { workspace = true } -uuid = { workspace = true } -thiserror = { workspace = true } -anyhow = { workspace = true } -chrono = { workspace = true } -reqwest = { workspace = true } -dirs = { workspace = true } -sha1_smol = { workspace = true } -sha2 = { workspace = true } -murmur2 = { workspace = true } -md5 = { workspace = true } -async_zip = { workspace = true } -discord-rich-presence = { workspace = true } -regex = { workspace = true } -sysinfo = { workspace = true } -whoami = { workspace = true } -lazy_static = { workspace = true } -async-tungstenite = { workspace = true } -async-recursion = { workspace = true } -tokio-stream = { workspace = true } -bytes = { workspace = true } -tempfile = { workspace = true } -rand = { workspace = true } -p256 = { workspace = true } -byteorder = { workspace = true } -base64 = { workspace = true } -flate2 = { workspace = true } -toml = { workspace = true } -url = { workspace = true } - -# features[tauri] -tauri = { workspace = true, optional = true } -tauri-specta = { workspace = true, optional = true } - -# features[specta] -specta = { workspace = true, optional = true } - -# features[cli] -indicatif = { workspace = true, optional = true } - -[build-dependencies] -dotenvy = { workspace = true } - -[target.'cfg(target_os = "windows")'.dependencies] -winreg = { workspace = true } - -[target.'cfg(target_os = "linux")'.dependencies] -onelauncher_gamemode = { workspace = true } diff --git a/packages/core_old/package.json b/packages/core_old/package.json deleted file mode 100644 index 16527a58..00000000 --- a/packages/core_old/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "@onelauncher/core_old", - "version": "0.1.0-alpha.2", - "description": "Next-generation open source Minecraft launcher core", - "license": "GPL-3.0-only", - "repository": { - "type": "git", - "url": "git@github.com:polyfrost/onelauncher", - "directory": "packages/core_old" - }, - "bugs": "https://github.com/polyfrost/onelauncher/issues", - "sideEffects": false, - "scripts": {} -} diff --git a/packages/core_old/src/api/cluster/content/logger.rs b/packages/core_old/src/api/cluster/content/logger.rs deleted file mode 100644 index 1f05a719..00000000 --- a/packages/core_old/src/api/cluster/content/logger.rs +++ /dev/null @@ -1,372 +0,0 @@ -//! `OneLauncher` log management - -use crate::data::{Credentials, Directories, MinecraftCredentials}; -use crate::prelude::ClusterPath; -use crate::utils::http; -use futures::TryFutureExt; -use onelauncher_utils::io::{self, IOError}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::time::SystemTime; -use tokio::io::{AsyncReadExt, AsyncSeekExt}; - -// TODO: put this in the global store -/// Core logging state and reader for `OneLauncher`. -#[derive(Serialize, Debug)] -pub struct LogManager { - /// Type log type associated with this log file. - pub log_type: LogType, - /// The age of this log as a [`u64`] in seconds. - pub age: u64, - /// The log file to read as a [`String`]. - pub log_file: String, - /// The parsed and censored output of the logfile. - pub output: Option, -} - -#[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, PartialEq)] -pub enum LogType { - Info, - Crash, -} - -impl LogManager { - /// Initialize a new [`LogManager`]. - async fn initialize( - log_type: LogType, - age: SystemTime, - cluster: &ClusterPath, - log_file: String, - clear: Option, - ) -> crate::Result { - Ok(Self { - log_type, - age: age - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap_or_else(|_| std::time::Duration::from_secs(0)) - .as_secs(), - output: if clear.unwrap_or(false) { - None - } else { - Some(get_output_by_file(cluster, log_type, &log_file).await?) - }, - log_file, - }) - } -} - -/// Verify all [`LogManager`]s of a certain [`LogType`] -#[tracing::instrument] -pub async fn get_logs_by_type( - cluster_path: &ClusterPath, - log_type: LogType, - clear: Option, - logs: &mut Vec>, -) -> crate::Result<()> { - let logs_folder = match log_type { - LogType::Info => Directories::cluster_logs_dir(cluster_path).await?, - LogType::Crash => Directories::crash_reports_dir(cluster_path).await?, - }; - - if logs_folder.exists() { - for entry in - std::fs::read_dir(&logs_folder).map_err(|e| IOError::with_path(e, &logs_folder))? - { - let entry: std::fs::DirEntry = - entry.map_err(|e| IOError::with_path(e, &logs_folder))?; - let age = entry - .metadata()? - .created() - .unwrap_or(SystemTime::UNIX_EPOCH); - let path = entry.path(); - if !path.is_file() { - continue; - } - if let Some(name) = path.file_name() { - let name = name.to_string_lossy().to_string(); - - logs.push(LogManager::initialize(log_type, age, cluster_path, name, clear).await); - } - } - } - - Ok(()) -} - -/// Get all [`LogManager`]s in the global [`State`]. -pub async fn get_logs( - cluster_path: &ClusterPath, - clear: Option, -) -> crate::Result> { - let dir = Directories::cluster_logs_dir(cluster_path).await?; - if !dir.exists() { - io::create_dir(dir).await?; - return Ok(vec![]); - } - - let mut logs = Vec::new(); - get_logs_by_type(cluster_path, LogType::Info, clear, &mut logs).await?; - get_logs_by_type(cluster_path, LogType::Crash, clear, &mut logs).await?; - - let mut logs = logs - .into_iter() - .collect::>>()?; - logs.sort_by(|a, b| match (a.log_file.as_str(), b.log_file.as_str()) { - ("latest.log", _) => std::cmp::Ordering::Less, - (_, "latest.log") => std::cmp::Ordering::Greater, - _ => b.age.cmp(&a.age).then(b.log_file.cmp(&a.log_file)), - }); - Ok(logs) -} - -/// Delete all stored logs from a specific [`ClusterPath`]. -#[tracing::instrument] -pub async fn delete_logs(cluster_path: ClusterPath) -> crate::Result<()> { - let cluster_path = cluster_path.cluster_path().await?; - let logs_folder = Directories::cluster_logs_dir(&cluster_path).await?; - for entry in std::fs::read_dir(&logs_folder).map_err(|e| IOError::with_path(e, &logs_folder))? { - let entry = entry.map_err(|e| IOError::with_path(e, &logs_folder))?; - let path = entry.path(); - if path.is_dir() { - io::remove_dir_all(&path).await?; - } - } - - Ok(()) -} - -/// Get the [`LogManager`] for a specific [`ClusterPath`] log file. -#[tracing::instrument] -pub async fn get_logs_by_file( - cluster_path: ClusterPath, - log_type: LogType, - log_file: String, -) -> crate::Result { - let cluster_path = cluster_path.cluster_path().await?; - - let path = match log_type { - LogType::Info => Directories::cluster_logs_dir(&cluster_path).await, - LogType::Crash => Directories::crash_reports_dir(&cluster_path).await, - }? - .join(&log_file); - - let metadata = std::fs::metadata(&path)?; - let age = metadata.created().unwrap_or(SystemTime::UNIX_EPOCH); - - LogManager::initialize(log_type, age, &cluster_path, log_file, Some(true)).await -} - -/// Get the default [`LogCursor`] for a [`ClusterPath`]. -#[tracing::instrument] -pub async fn get_log_cursor(cluster_path: ClusterPath, cursor: u64) -> crate::Result { - get_live_log_cursor(cluster_path, "latest.log", cursor).await -} - -/// Get a live [`LogCursor`] for a [`ClusterPath`]'s log file. -#[tracing::instrument] -pub async fn get_live_log_cursor( - cluster_path: ClusterPath, - log_file: &str, - mut cursor: u64, -) -> crate::Result { - let cluster_path = cluster_path.cluster_path().await?; - let logs_folder = Directories::cluster_logs_dir(&cluster_path).await?; - let path = logs_folder.join(log_file); - if !path.exists() { - return Ok(LogCursor { - cursor: 0, - new: false, - output: LogOutput(String::new()), - }); - } - - let mut file = tokio::fs::File::open(&path) - .await - .map_err(|e| IOError::with_path(e, &path))?; - let metadata = file - .metadata() - .await - .map_err(|e| IOError::with_path(e, &path))?; - - let new = if cursor > metadata.len() { - cursor = 0; - true - } else { - false - }; - - let mut buf = Vec::new(); - file.seek(std::io::SeekFrom::Start(cursor)) - .map_err(|e| IOError::with_path(e, &path)) - .await?; - let bytes_read = file - .read_to_end(&mut buf) - .map_err(|e| IOError::with_path(e, &path)) - .await?; - let output = String::from_utf8_lossy(&buf).to_string(); - let cursor = cursor + bytes_read as u64; - let creds: Vec = crate::State::get() - .await? - .users - .read() - .await - .users - .clone() - .into_values() - .collect(); - let output = LogOutput::censor_secrets(output, &creds, &None); - Ok(LogCursor { - cursor, - output, - new, - }) -} - -/// Delete a specific minecraft log file. -#[tracing::instrument] -pub async fn delete_logs_by_file( - cluster_path: ClusterPath, - log_type: LogType, - log_file: &str, -) -> crate::Result<()> { - let cluster_path = cluster_path.cluster_path().await?; - let logs_folder = match log_type { - LogType::Info => Directories::cluster_logs_dir(&cluster_path).await?, - LogType::Crash => Directories::crash_reports_dir(&cluster_path).await?, - }; - - let path = logs_folder.join(log_file); - io::remove_file(&path).await?; - Ok(()) -} - -pub async fn read_log_to_string(path: &std::path::PathBuf) -> crate::Result { - if let Some(ext) = path.extension() { - let mut result = String::new(); - - if ext == "gz" { - result = io::read_gz_to_string(path).await?; - } else if ext == "log" || ext == "txt" { - // On Java 17 and older, UTF-8 is not the default charset (https://openjdk.org/jeps/400) - // Minecraft on Windows on older versions sometimes likes to output the log - // in an encoding other than UTF-8, which would make OneLauncher throw an error - // when attempting to read it into a String (Rust Strings are UTF-8) - // TODO: Potentially explore a better solution for non UTF-8 log reading? - let bytes = io::read(path).await?; - result = String::from_utf8_lossy(&bytes).to_string(); - } - - return Ok(result); - } - - Err(anyhow::anyhow!("log file extension {} not supported", path.display()).into()) -} - -/// Get a specific [`ClusterPath`] log file's [`LogOutput`]. -#[tracing::instrument] -pub async fn get_output_by_file( - cluster_path: &ClusterPath, - log_type: LogType, - log_file: &str, -) -> crate::Result { - let logs_folder = match log_type { - LogType::Info => Directories::cluster_logs_dir(cluster_path).await?, - LogType::Crash => Directories::crash_reports_dir(cluster_path).await?, - }; - let path = logs_folder.join(log_file); - let credentials: Vec = crate::State::get() - .await? - .users - .read() - .await - .users - .clone() - .into_values() - .collect(); - - let result = read_log_to_string(&path).await?; - - return Ok(LogOutput::censor_secrets(result, &credentials, &None)); -} - -/// The log cursor used to parse logs passed into [`LogManager`]. -#[derive(Serialize, Debug)] -pub struct LogCursor { - /// The cursor ID. - pub cursor: u64, - /// The [`LogOutput`] associated with this cursor. - pub output: LogOutput, - /// Whether or not this corresponds is a new log file. - pub new: bool, -} - -/// The log output, a wrapper around [`String`], with utilities for parsing and censoring log contents. -#[derive(Serialize, Debug)] -#[serde(transparent)] -pub struct LogOutput(pub String); - -impl LogOutput { - /// Censor user secrets because sometimes mclogs misses them, including username, realname and minecraft credentials. - pub fn censor_secrets( - mut output: String, - credentials: &Vec, - _credentials_store: &Option, - ) -> Self { - let username = whoami::username(); - let realname = whoami::realname(); - output = output.replace(&format!("/{username}/"), "/{ENV_USERNAME}/"); - output = output.replace(&format!("\\{username}\\"), "\\{ENV_USERNAME}\\"); - output = output.replace(&format!("/{realname}/"), "/{ENV_REALNAME}/"); - output = output.replace(&format!("\\{realname}\\"), "\\{ENV_REALNAME}\\"); - - for cred in credentials { - output = output.replace(&cred.access_token, "{MC_ACCESS_TOKEN}"); - output = output.replace(&cred.username, "{MC_USERNAME}"); - output = output.replace(&cred.id.as_simple().to_string(), "{MC_UUID}"); - output = output.replace(&cred.id.as_hyphenated().to_string(), "{MC_UUID}"); - } - - Self(output) - } -} - -#[derive(serde::Deserialize)] -struct McLogsResponse { - pub success: bool, - #[serde(default)] - pub id: Option, - #[serde(default)] - pub error: Option, -} - -/// Upload a log to https://mclo.gs/. If successful, it returns the log id -#[tracing::instrument] -pub async fn upload_log(path: &ClusterPath, log: LogOutput) -> crate::Result { - let log = log.0; - let mut form = HashMap::new(); - form.insert("content", log); - - let parsed = serde_json::from_slice::( - &http::REQWEST_CLIENT - .post(format!("{}/log", crate::constants::MCLOGS_API_URL)) - .header("Content-Type", "application/x-www-form-urlencoded") - .form(&form) - .send() - .await? - .bytes() - .await?, - )?; - - if parsed.success { - parsed.id.map_or_else( - || Err(anyhow::anyhow!("failed to get log id from mclo.gs").into()), - Ok, - ) - } else { - Err(anyhow::anyhow!( - "failed to upload log to mclo.gs: {}", - parsed.error.unwrap_or_default() - ) - .into()) - } -} diff --git a/packages/core_old/src/api/cluster/content/mod.rs b/packages/core_old/src/api/cluster/content/mod.rs deleted file mode 100644 index a3c0224f..00000000 --- a/packages/core_old/src/api/cluster/content/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod logger; -pub mod package; -pub mod screenshots; -pub mod worlds; diff --git a/packages/core_old/src/api/cluster/content/package.rs b/packages/core_old/src/api/cluster/content/package.rs deleted file mode 100644 index adc1a36e..00000000 --- a/packages/core_old/src/api/cluster/content/package.rs +++ /dev/null @@ -1,297 +0,0 @@ -#![allow(clippy::significant_drop_tightening)] - -use crate::data::{Loader, ManagedPackage, ManagedVersion, PackageType}; -use crate::prelude::PackagePath; -use crate::processor::Cluster; -use crate::proxy::send::{init_ingress, send_ingress, send_internet}; -use crate::store::{ClusterPath, ManagedVersionFile, Package, PackageMetadata}; -use crate::utils::http; -use crate::{Result, State}; -use onelauncher_utils::io; -use reqwest::Method; -// TODO: Implement proper error handling - -/// Find a managed version using filters -/// - Game Version (Default: Cluster's MC Version) -/// - Loader (Default: Cluster's chosen Loader) -/// - Package Version (Default: Latest version available) -#[tracing::instrument] -pub async fn find_managed_version( - package: &ManagedPackage, - game_version: Option, - loader: Option, - package_version: Option, -) -> Result { - let provider = package.provider.clone(); - - let versions = provider - .get_all_versions( - &package.id, - game_version.as_ref().map(|v| vec![v.to_owned()]).clone(), - loader.map(|l| vec![l]).clone(), - None, - None, - ) - .await?; - - Ok(versions.0 - .iter() - .find(|v| { - if let Some(package_version) = package_version.as_ref() { - return v.id == *package_version; - } - - let check_game_version = game_version - .as_ref() - .is_some_and(|gv| v.game_versions.iter().any(|gv2| *gv2 == *gv)); - - let check_loader = loader - .as_ref() - .is_some_and(|loader| v.loaders.iter().any(|l| *l == *loader)); - - check_game_version && check_loader - }) - .ok_or_else(|| anyhow::anyhow!("no matching version found")) - .cloned()?) -} - -/// Download a package to a cluster. Supports filtering by: -/// - Game Version (Default: Cluster's MC Version) -/// - Loader (Default: Cluster's chosen Loader) -/// - Package Version (Default: Latest version available) -#[tracing::instrument(skip(package, cluster))] -pub async fn download_package( - package: &ManagedPackage, - cluster: &mut Cluster, - game_version: Option, - loader: Option, - package_version: Option, -) -> Result<(PackagePath, Package)> { - tracing::info!( - "preparing package '{}' for cluster '{}'", - package.title, - cluster.meta.name - ); - send_internet(crate::proxy::InternetPayload::InstallPackage { - id: package.id.clone(), - }) - .await?; - - let loader = loader.unwrap_or(cluster.meta.loader); - let game_version = game_version.unwrap_or_else(|| cluster.meta.mc_version.clone()); - - let managed_version = - find_managed_version(package, Some(game_version), Some(loader), package_version).await?; - - let file = managed_version - .get_primary_file() - .ok_or_else(|| anyhow::anyhow!("no primary file found"))?; - - let package_path = download_file(package, &managed_version, file, &package.package_type, cluster).await?; - let sha1 = file.hashes.get("sha1").unwrap_or(&String::new()).clone(); - - let package = Package { - file_name: file.file_name.clone(), - sha1, - meta: PackageMetadata::from_managed_package(package.clone(), managed_version), - disabled: false, - }; - - Ok((package_path, package)) -} - -/// Download a file to a cluster from a managed version file. -#[tracing::instrument(skip_all)] -async fn download_file( - package: &ManagedPackage, - version: &ManagedVersion, - file: &ManagedVersionFile, - package_type: &PackageType, - cluster: &Cluster, -) -> Result { - // TODO: Implement hash checking - let cluster_path = &cluster.get_full_path().await?; - - let path = &cluster_path - .join(package_type.get_folder()) - .join(&file.file_name); - - let ingress_id = init_ingress( - crate::IngressType::DownloadPackage { - cluster_path: cluster_path.to_owned(), - package_name: package.title.clone(), - icon: None, - package_id: Some(package.id.clone()), - package_version: Some(version.id.clone()), - }, 100.0, "downloading package").await?; - - tracing::info!( - "downloading file '{}' version '{}'", - file.file_name, - version.name - ); - - let state = State::get().await?; - let bytes = http::fetch_advanced( - Method::GET, - &file.url, - file.hashes.get("sha1").map(String::as_str), - None, - None, - Some((&ingress_id, 90.0)), - &state.fetch_semaphore, - ) - .await?; - - if let Err(err) = send_ingress(&ingress_id, 5.0, Some("saving file")).await { - tracing::error!("{err}"); - } - - if let Err(err) = http::write(&path, &bytes, &state.io_semaphore).await { - tracing::error!("failed to write file to cluster: {err}"); - if path.exists() { - let _ = io::remove_file(&path).await; - } - - return Err(err); - }; - - if let Err(err) = send_ingress(&ingress_id, 100.0, Some(format!("downloaded package {}", package.title).as_str())).await { - tracing::error!("{err}"); - } - - drop(state); - - Ok(PackagePath::from_path(path).await?) -} - -/// Add a package to a cluster. -#[tracing::instrument] -pub async fn add_package( - cluster_path: &ClusterPath, - package_path: PackagePath, - package: Package, -) -> Result<()> { - let state = State::get().await?; - let mut manager = state.packages.write().await; - let manager = manager - .get_mut(cluster_path) - .ok_or_else(|| anyhow::anyhow!("cluster not found in packages map"))?; - - manager - .add_package(package_path, package) - .await?; - - Ok(()) -} - -/// Remove a package from a cluster. -#[tracing::instrument] -pub async fn remove_package( - cluster_path: &ClusterPath, - package_path: &PackagePath, -) -> Result<()> { - let state = State::get().await?; - let mut manager = state.packages.write().await; - let manager = manager - .get_mut(cluster_path) - .ok_or_else(|| anyhow::anyhow!("cluster not found in packages map"))?; - - manager.remove_package(package_path).await?; - - Ok(()) -} - -#[tracing::instrument] -pub async fn set_package_enabled( - cluster_path: &ClusterPath, - package_path: &PackagePath, - enabled: bool, -) -> Result<()> { - let state = State::get().await?; - let mut manager = state.packages.write().await; - let manager = manager - .get_mut(cluster_path) - .ok_or_else(|| anyhow::anyhow!("cluster not found in packages map"))?; - - manager.set_package_enabled(package_path, enabled).await?; - - Ok(()) -} - -/// Get a package from a cluster. -#[tracing::instrument] -pub async fn get_package( - cluster_path: &ClusterPath, - package_path: &PackagePath, - package_type: PackageType, -) -> Result { - let state = State::get().await?; - let mut store = state.packages.write().await; - let manager = store - .get_mut(cluster_path) - .ok_or_else(|| anyhow::anyhow!("cluster not found in packages map"))?; - - Ok(manager - .get(package_type) - .await - .packages - .get(package_path) - .cloned() - .ok_or_else(|| anyhow::anyhow!("package not found"))?) -} - -/// Get packages from a cluster. -#[tracing::instrument] -pub async fn get_packages( - cluster_path: &ClusterPath, - package_type: PackageType, -) -> Result> { - let state = State::get().await?; - let mut store = state.packages.write().await; - let manager = store - .get_mut(cluster_path) - .ok_or_else(|| anyhow::anyhow!("cluster not found in packages map"))?; - - Ok(manager - .get(package_type) - .await - .packages - .values() - .cloned() - .collect()) -} - -/// Sync packages from a cluster. -#[tracing::instrument] -pub async fn sync_packages(cluster_path: &ClusterPath) -> Result<()> { - let state = State::get().await?; - let mut manager = state.packages.write().await; - let manager = manager - .get_mut(cluster_path) - .ok_or_else(|| anyhow::anyhow!("cluster not found in packages map"))?; - - manager.sync_packages(&state.directories).await; - - Ok(()) -} - -/// Sync packages from a cluster. -#[tracing::instrument] -pub async fn sync_packages_by_type( - cluster_path: &ClusterPath, - package_type: PackageType, - clear: Option -) -> Result<()> { - let state = State::get().await?; - let mut manager = state.packages.write().await; - let manager = manager - .get_mut(cluster_path) - .ok_or_else(|| anyhow::anyhow!("cluster not found in packages map"))?; - - manager - .sync_packages_by_type(&state.directories, package_type, clear) - .await?; - - Ok(()) -} diff --git a/packages/core_old/src/api/cluster/content/screenshots.rs b/packages/core_old/src/api/cluster/content/screenshots.rs deleted file mode 100644 index e8f9ef79..00000000 --- a/packages/core_old/src/api/cluster/content/screenshots.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::Result; -use onelauncher_utils::io; - -use crate::store::ClusterPath; - -/// Gets a list of screenshot file names from the [`ClusterPath`] -#[tracing::instrument] -pub async fn get_screenshots(cluster: &ClusterPath) -> Result> { - let dir = cluster.full_path().await?.join("screenshots"); - - if !dir.exists() { - io::create_dir(dir).await?; - return Ok(vec![]); - } - - let mut list = vec![]; - let mut files = io::read_dir(dir).await?; - while let Some(file) = files.next_entry().await? { - list.push(file.file_name().to_string_lossy().to_string()); - } - - Ok(list) -} diff --git a/packages/core_old/src/api/cluster/content/worlds.rs b/packages/core_old/src/api/cluster/content/worlds.rs deleted file mode 100644 index 719a3863..00000000 --- a/packages/core_old/src/api/cluster/content/worlds.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::Result; -use onelauncher_utils::io; - -use crate::store::ClusterPath; - -/// Gets a list of screenshot file names from the [`ClusterPath`] -#[tracing::instrument] -pub async fn get_worlds(cluster: &ClusterPath) -> Result> { - let dir = cluster.full_path().await?.join("saves"); - - if !dir.exists() { - io::create_dir(dir).await?; - return Ok(vec![]); - } - - let mut list = vec![]; - let mut files = io::read_dir(dir).await?; - while let Some(file) = files.next_entry().await? { - list.push(file.file_name().to_string_lossy().to_string()); - } - - Ok(list) -} diff --git a/packages/core_old/src/api/cluster/create.rs b/packages/core_old/src/api/cluster/create.rs deleted file mode 100644 index 5f0709e5..00000000 --- a/packages/core_old/src/api/cluster/create.rs +++ /dev/null @@ -1,333 +0,0 @@ -//! Utilities for creating Clusters - -use crate::data::Loader; -use crate::package::from::CreatePackCluster; -use crate::proxy::send::send_cluster; -use crate::proxy::ClusterPayloadType; -pub use crate::store::{Cluster, ClusterPath, JavaOptions, PackageData, State}; -use crate::{cluster, package}; -use interpulse::api::modded::LoaderVersion; -use onelauncher_utils::io::{self, canonicalize}; -use std::path::PathBuf; - -/// Creates an unfinished [`Cluster`] used for building onto it and adds it to the memory [`State`]. -/// Returns a relative filepath ([`ClusterPath`]) which can be used to access the cluster. -#[tracing::instrument] -#[onelauncher_macros::memory] -#[allow(clippy::too_many_arguments)] -pub async fn create_unfinished_cluster( - mut name: String, -) -> crate::Result { - name = cluster::sanitize_cluster_name(&name); - tracing::trace!("creating new cluster {}", name); - let state = State::get().await?; - let uuid = uuid::Uuid::new_v4(); - let mut path = state.directories.clusters_dir().await.join(&name); - - // i hate it here - if path.exists() { - let mut new_name; - let mut new_path; - let mut which = 1; - loop { - new_name = format!("{name} ({which})"); - new_path = state.directories.clusters_dir().await.join(&new_name); - if !new_path.exists() { - break; - } - which += 1; - } - - tracing::debug!( - "collision while creating new cluster: {}, renaming to {}", - path.display(), - new_path.display() - ); - path = new_path; - name = new_name; - } - - io::create_dir_all(&path).await?; - tracing::info!( - "creating cluster at path {}", - &canonicalize(&path)?.display() - ); - - let cluster = Cluster::new(uuid, name, String::from("none")).await?; - let result = async { - { - state - .clusters - .write() - .await - .insert(cluster.clone(), false) - .await?; - } - - State::sync().await?; - let state = State::get().await?; - let mut packages = state.packages.write().await; - packages.add_cluster(cluster.cluster_path()); - - Ok(cluster.cluster_path()) - } - .await; - - match result { - Ok(cluster) => Ok(cluster), - Err(err) => { - let _ = cluster::remove(&cluster.cluster_path()).await; - - Err(err) - } - } -} - -/// Creates a [`Cluster`] and adds it to the memory [`State`]. -/// Returns a relative filepath ([`ClusterPath`]) which can be used to access the cluster. -#[tracing::instrument] -#[onelauncher_macros::memory] -#[allow(clippy::too_many_arguments)] -pub async fn create_cluster( - name: String, - mc_version: String, - mod_loader: Loader, - loader_version: Option, - icon: Option, - icon_url: Option, - package_data: Option, - skip: Option, - skip_watch: Option, -) -> crate::Result { - let mut cluster = cluster::get(&create_unfinished_cluster(name).await?) - .await? - .ok_or(anyhow::anyhow!("failed to get cluster"))?; - - let state = State::get().await?; - - let loader = if mod_loader == Loader::Vanilla { - None - } else { - get_loader_version(mc_version.clone(), mod_loader, loader_version).await? - }; - - let result = async { - cluster.meta.mc_version = mc_version; - cluster.meta.loader = mod_loader; - cluster.meta.loader_version = loader.clone(); - - if let Some(ref icon) = icon { - let bytes = io::read(state.directories.caches_dir().await.join(icon)).await?; - cluster - .set_icon_bytes( - &state.directories.caches_dir().await, - &state.io_semaphore, - bytes::Bytes::from(bytes), - &icon.to_string_lossy(), - ) - .await?; - } - - cluster.meta.icon_url = icon_url; - if let Some(loader_version) = loader { - cluster.meta.loader = mod_loader; - cluster.meta.loader_version = Some(loader_version); - } - - cluster.meta.package_data = package_data; - if let Some(package_data) = &mut cluster.meta.package_data { - package_data.locked = - Some(package_data.package_id.is_some() && package_data.version_id.is_some()); - } - - send_cluster( - cluster.uuid, - &cluster.cluster_path(), - &cluster.meta.name, - ClusterPayloadType::Created, - ) - .await?; - - { - state - .clusters - .write() - .await - .insert(cluster.clone(), skip_watch.unwrap_or_default()) - .await?; - } - - if !skip.unwrap_or(false) { - // let cluster = cluster.clone(); - // tokio::task::spawn(async move { - if let Err(err) = crate::game::install_minecraft(&cluster, None, false).await { - tracing::error!( - "failed to install Minecraft while creating cluster {}: {}", - cluster.uuid, - err - ); - } - // }); - } - - State::sync().await?; - let state = State::get().await?; - let mut packages = state.packages.write().await; - packages.add_cluster(cluster.cluster_path()); - - Ok(cluster.cluster_path()) - } - .await; - - match result { - Ok(cluster) => Ok(cluster), - Err(err) => { - let _ = cluster::remove(&cluster.cluster_path()).await; - - Err(err) - } - } -} - -/// Create a [`Cluster`] from a [`CreatePackCluster`] cluster, returning a [`ClusterPath`]. -pub async fn create_cluster_from_package(cluster: CreatePackCluster) -> crate::Result { - create_cluster( - cluster.name, - cluster.mc_version, - cluster.mod_loader, - cluster.loader_version, - cluster.icon, - cluster.icon_url, - cluster.package_data, - cluster.skip, - cluster.skip_watch, - ) - .await -} - -/// Create a duplicate [`Cluster`] from another [`ClusterPath`], returning a new [`ClusterPath`] -pub async fn create_cluster_from_duplicate(from: ClusterPath) -> crate::Result { - let cluster = cluster::get(&from).await?.ok_or_else(|| { - anyhow::anyhow!("failed to get unmanaged cluster from {}", from.to_string()) - })?; - let cluster_path = create_cluster( - cluster.meta.name.clone(), - cluster.meta.mc_version.clone(), - cluster.meta.loader, - cluster.meta.loader_version.clone().map(|it| it.id), - cluster.meta.icon.clone(), - cluster.meta.icon_url.clone(), - cluster.meta.package_data.clone(), - Some(true), - Some(true), - ) - .await?; - - let copied = package::import::copy_minecraft( - cluster_path.clone(), - from.full_path().await?, - &State::get().await?.io_semaphore, - None, - ) - .await?; - - let duplicated = cluster::get(&cluster_path).await?.ok_or_else(|| { - anyhow::anyhow!( - "failed to get unmanaged cluster from {}", - cluster_path.to_string() - ) - })?; - - crate::game::install_minecraft(&duplicated, Some(copied), false).await?; - { - let state = State::get().await?; - let mut watcher = state.watcher.write().await; - Cluster::watch(&cluster.get_full_path().await?, &mut watcher).await?; - } - - send_cluster( - cluster.uuid, - &cluster.cluster_path(), - &cluster.meta.name, - ClusterPayloadType::Edited, - ) - .await?; - - State::sync().await?; - Ok(cluster_path) -} - -/// Get the latest [`LoaderVersion`] from a [`Loader`]. -#[tracing::instrument] -#[onelauncher_macros::memory] -pub(crate) async fn get_loader_version( - mc_version: String, - loader: Loader, - loader_version: Option, -) -> crate::Result> { - let state = State::get().await?; - let metadata = state.metadata.read().await; - let version = loader_version.unwrap_or_else(|| "latest".to_string()); - let filter = |it: &LoaderVersion| match version.as_str() { - "latest" => true, - "stable" => it.stable, - id => { - it.id == *id - || format!("{mc_version}-{id}") == it.id - || format!("{mc_version}-{id}-{mc_version}") == it.id - } - }; - - let loader_meta = match loader { - Loader::Forge => &metadata.forge, - Loader::Fabric => &metadata.fabric, - Loader::Quilt => &metadata.quilt, - Loader::NeoForge => &metadata.neoforge, - // Loader::LegacyFabric => &metadata.legacy_fabric, - _ => return Err(CreateClusterError::MissingManifest(loader.to_string()).into()), - }; - - let loader_meta = loader_meta - .to_owned() - .ok_or_else(|| anyhow::anyhow!("couldn't get game versions"))?; - - let loaders = &loader_meta - .game_versions - .iter() - .find(|it| { - it.id - .replace(interpulse::api::modded::DUMMY_REPLACE_STRING, &mc_version) - == mc_version - }) - .ok_or_else(|| { - CreateClusterError::UnsupportedLoader(loader.to_string(), mc_version.clone()) - })? - .loaders; - - let loader_version = loaders - .iter() - .find(|&it| filter(it)) - .cloned() - .or_else(|| { - if version == "stable" { - loaders.iter().next().cloned() - } else { - None - } - }) - .ok_or_else(|| CreateClusterError::InvalidLoaderVersion(version, loader.to_string()))?; - - Ok(Some(loader_version)) -} - -#[derive(thiserror::Error, Debug)] -pub enum CreateClusterError { - #[error("Loader {0} is unsupported on Minecraft version {1}")] - UnsupportedLoader(String, String), - #[error("Invalid Loader version {0} for Loader {1}")] - InvalidLoaderVersion(String, String), - #[error("Failed to get Loader manifest for {0}.")] - MissingManifest(String), - #[error("Failed to handle I/O operations: {0}")] - IOError(#[from] std::io::Error), -} diff --git a/packages/core_old/src/api/cluster/mod.rs b/packages/core_old/src/api/cluster/mod.rs deleted file mode 100644 index 8d85134a..00000000 --- a/packages/core_old/src/api/cluster/mod.rs +++ /dev/null @@ -1,436 +0,0 @@ -//! **`OneLauncher` Cluster** -//! -//! API for creating our managed Minecraft instances, Clusters. - -// TODO: (pauline) fully implement the cluster::self APIs. - -use crate::proxy::send::send_cluster; - -use crate::prelude::{ClusterPath, JavaVersion, PackagePath}; -use crate::proxy::ClusterPayloadType; -pub use crate::store::{Cluster, JavaOptions, State}; -use crate::store::{MinecraftCredentials, ProcessorChild}; - -use onelauncher_utils::io::{self, IOError}; - -use std::collections::HashMap; -use std::future::Future; - -use std::path::{Path, PathBuf}; -use std::sync::Arc; - -use tokio::process::Command; -use tokio::sync::RwLock; - -pub mod content; -pub mod create; -pub mod update; - -/// get a cluster by its specified [`ClusterPath`]. -#[tracing::instrument] -pub async fn get(path: &ClusterPath) -> crate::Result> { - let state = State::get().await?; - let clusters = state.clusters.read().await; - let cluster = clusters.0.get(path).cloned(); - - Ok(cluster) -} - -/// get a cluster by its specified [`ClusterPath`]. -#[tracing::instrument] -pub async fn get_by_name(name: &str) -> crate::Result> { - let state = State::get().await?; - let clusters = state.clusters.read().await; - let cluster = clusters.0.values().find(|c| c.meta.name == name).cloned(); - - Ok(cluster) -} - -/// get a list of all [`Cluster`]s -#[tracing::instrument] -pub async fn list() -> crate::Result> { - let state = State::get().await?; - let clusters = state.clusters.read().await; - Ok(clusters.0.clone().into_iter().map(|it| it.1).collect()) -} - -/// get a Map of [`Cluster`] group names containing lists of [`Cluster`]s -#[tracing::instrument] -pub async fn list_grouped() -> crate::Result>> { - // TODO: This can 100% be made better - let list = list().await?; - let mut map = HashMap::>::new(); - - for cluster in list { - let group = cluster - .meta - .group - .clone() - .unwrap_or_else(|| String::from("Ungrouped")); - if let Some(items) = map.get_mut(&group) { - items.push(cluster); - } else { - map.insert(group, vec![cluster]); - } - } - - Ok(map) -} - -/// run a Minecraft [`Cluster`] using the default credentials. -#[tracing::instrument] -pub async fn run_default(path: &ClusterPath) -> crate::Result>> { - let state = State::get().await?; - let default = { - let mut users = state.users.write().await; - users - .get_default() - .await? - .ok_or_else(|| anyhow::anyhow!("no default credentials found!"))? - }; - - run_credentials(path, &default).await -} - -/// run a Minecraft [`Cluster`] using [`MinecraftCredentials`] for authentication. -/// returns an [`Arc`] pointer to [`RwLock`] to [`ProcessorChild`]. -#[tracing::instrument(skip(creds))] -#[onelauncher_macros::memory] -pub async fn run( - path: &ClusterPath, - creds: Option<&MinecraftCredentials>, -) -> crate::Result>> { - if let Some(creds) = creds { - run_credentials(path, creds).await - } else { - run_default(path).await - } -} - -/// run a Minecraft [`Cluster`] using [`MinecraftCredentials`] for authentication. -/// returns an [`Arc`] pointer to [`RwLock`] to [`ProcessorChild`]. -#[tracing::instrument(skip(creds))] -#[onelauncher_macros::memory] -pub async fn run_credentials( - path: &ClusterPath, - creds: &MinecraftCredentials, -) -> crate::Result>> { - let state = State::get().await?; - let settings = state.settings.read().await; - let cluster = get(path) - .await? - .ok_or_else(|| anyhow::anyhow!("failed to run a nonexistent cluster at path {}", path))?; - - let pre = &cluster - .init_hooks - .as_ref() - .unwrap_or(&settings.init_hooks) - .pre - .as_ref() - .and_then(|it| { - if it.is_empty() { - None - } else { - Some(it.clone()) - } - }); - - if let Some(hook) = pre { - // TODO: hook parameters - let mut cmd = hook.split(' '); - if let Some(command) = cmd.next() { - let full_path = path.full_path().await?; - let result = Command::new(command) - .args(cmd) - .current_dir(&full_path) - .spawn() - .map_err(|e| IOError::with_path(e, &full_path))? - .wait() - .await - .map_err(IOError::from)?; - - if !result.success() { - return Err(anyhow::anyhow!( - "non-zero exit code for pre-launch hook: {}", - result.code().unwrap_or(-1) - ) - .into()); - } - } - } - - let java_args = cluster - .java - .as_ref() - .and_then(|it| it.custom_arguments.as_ref()) - .unwrap_or(&settings.custom_java_args); - - let wrapper = cluster - .init_hooks - .as_ref() - .map_or(&settings.init_hooks.wrapper, |it| &it.wrapper) - .as_ref() - .and_then(|it| { - if it.is_empty() { - None - } else { - Some(it.clone()) - } - }); - - let memory = cluster.memory.unwrap_or(settings.memory); - let resolution = cluster.resolution.unwrap_or(settings.resolution); - let env_args = cluster - .java - .as_ref() - .and_then(|it| it.custom_env_arguments.as_ref()) - .unwrap_or(&settings.custom_env_args); - let post = cluster - .init_hooks - .as_ref() - .unwrap_or(&settings.init_hooks) - .post - .clone(); - let mut mc_options: Vec<(String, String)> = vec![]; - if let Some(fullscreen) = cluster.force_fullscreen { - mc_options.push(("fullscreen".to_string(), fullscreen.to_string())); - } else if settings.force_fullscreen { - mc_options.push(("fullscreen".to_string(), "true".to_string())); - } - - let process = crate::game::launch_minecraft( - &cluster, - java_args, - env_args, - &mc_options, - post, - creds, - &resolution, - &memory, - &wrapper, - ) - .await?; - - Ok(process) -} - -/// remove a specified cluster from it's [`ClusterPath`]. -#[tracing::instrument] -pub async fn remove(path: &ClusterPath) -> crate::Result<()> { - let state = State::get().await?; - let mut clusters = state.clusters.write().await; - - if let Some(cluster) = clusters.remove(path).await? { - send_cluster( - cluster.uuid, - path, - &cluster.meta.name, - ClusterPayloadType::Deleted, - ) - .await?; - } - - Ok(()) -} - -/// get a cluster by it's [`uuid::Uuid`] -#[tracing::instrument] -pub async fn get_by_uuid(uuid: uuid::Uuid) -> crate::Result> { - let state = State::get().await?; - let clusters = state.clusters.read().await; - let cluster = clusters.0.values().find(|c| c.uuid == uuid).cloned(); - - Ok(cluster) -} - -/// get a cluster's full path by it's [`ClusterPath`]. -#[tracing::instrument] -pub async fn get_full_path(path: &ClusterPath) -> crate::Result { - let _ = get(path).await?.ok_or_else(|| { - anyhow::anyhow!("failed to get the full path of cluster at path {}", path) - })?; - let full_path = io::canonicalize(path.full_path().await?)?; - - Ok(full_path) -} - -/// get a specific mod's full path in the filesystem by it's [`ClusterPath`] and [`PackagePath`]. -#[tracing::instrument] -pub async fn get_package_full_path( - cluster_path: &ClusterPath, - package_path: &PackagePath, -) -> crate::Result { - if get(cluster_path).await?.is_some() { - let full_path = io::canonicalize(package_path.full_path(cluster_path).await?)?; - - return Ok(full_path); - } - - Err(anyhow::anyhow!( - "failed to get the full path of a cluster at path {}", - package_path - .full_path(cluster_path) - .await? - .display() - ) - .into()) -} - -/// edit a cluster with an async closure and it's [`ClusterPath`] -pub async fn edit( - path: &ClusterPath, - action: impl Fn(&mut Cluster) -> FutFn, -) -> crate::Result<()> -where - FutFn: Future>, -{ - let state = State::get().await?; - let mut clusters = state.clusters.write().await; - - match clusters.0.get_mut(path) { - Some(ref mut cluster) => { - action(cluster).await?; - - send_cluster( - cluster.uuid, - path, - &cluster.meta.name, - ClusterPayloadType::Edited, - ) - .await?; - - Ok(()) - } - None => Err(anyhow::anyhow!("unmanaged cluster edited at {}", path.to_string()).into()), - } -} - -/// update a [`Cluster`]'s icon -pub async fn edit_icon(path: &ClusterPath, icon_path: Option<&Path>) -> crate::Result<()> { - let state = State::get().await?; - let result = if let Some(icon) = icon_path { - let bytes = io::read(icon).await?; - let mut clusters = state.clusters.write().await; - match clusters.0.get_mut(path) { - Some(ref mut cluster) => { - cluster - .set_icon_bytes( - &state.directories.caches_dir().await, - &state.io_semaphore, - bytes::Bytes::from(bytes), - &icon.to_string_lossy(), - ) - .await?; - - send_cluster( - cluster.uuid, - path, - &cluster.meta.name, - ClusterPayloadType::Edited, - ) - .await?; - Ok(()) - } - None => Err(anyhow::anyhow!( - "failed to update unmanaged cluster at {}", - path.to_string() - ) - .into()), - } - } else { - edit(path, |cluster| { - cluster.meta.icon = None; - async { Ok(()) } - }) - .await?; - State::sync().await?; - - Ok(()) - }; - - State::sync().await?; - result -} - -/// gets the optimal java version for a given [`Cluster`]. -pub async fn get_optimal_java_version(path: &ClusterPath) -> crate::Result> { - let state = State::get().await?; - if let Some(cluster) = get(path).await? { - let metadata = state.metadata.read().await; - let minecraft_metadata = metadata - .minecraft - .clone() - .ok_or_else(|| anyhow::anyhow!("couldn't get minecraft metadata"))?; - - let version = minecraft_metadata - .versions - .iter() - .find(|it| it.id == cluster.meta.mc_version) - .ok_or_else(|| { - anyhow::anyhow!( - "invalid or unknown Minecraft version {}", - cluster.meta.mc_version - ) - })?; - - let version_info = crate::game::metadata::download_version_info( - &state, - version, - cluster.meta.loader_version.as_ref(), - None, - None, - ) - .await?; - - let version = crate::game::java_version_from_cluster(&cluster, &version_info).await?; - - Ok(version) - } else { - Err(anyhow::anyhow!( - "failed to get the java version of unmanaged cluster at {}", - path.to_string() - ) - .into()) - } -} - -/// Try to update a [`Cluster`]'s playtime. -#[tracing::instrument] -#[onelauncher_macros::memory] -pub async fn update_playtime(path: &ClusterPath) -> crate::Result<()> { - let state = State::get().await?; - let cluster = get(path) - .await? - .ok_or_else(|| anyhow::anyhow!("failed to update playtime at path {}", path))?; - let recent_playtime = cluster.meta.recently_played; - - /* - * todo - */ - - let mut clusters = state.clusters.write().await; - if let Some(cluster) = clusters.0.get_mut(path) { - cluster.meta.overall_played += recent_playtime; - cluster.meta.recently_played = 0; - } - drop(clusters); - - State::sync().await?; - - Ok(()) -} - -/// This forces the cluster to be verified (and possibly reinstalled). -pub async fn repair_cluster(path: &ClusterPath) -> crate::Result<()> { - let cluster = get(path) - .await? - .ok_or_else(|| anyhow::anyhow!("failed to repair cluster at path {}", path))?; - - crate::game::install_minecraft(&cluster, None, true).await -} - -/// Sanitize a user-inputted [`Cluster`] name. -#[must_use] -pub fn sanitize_cluster_name(input: &str) -> String { - input.replace(['/', '\\', '?', '*', ':', '\'', '\"', '|', '<', '>'], "_") -} diff --git a/packages/core_old/src/api/cluster/update.rs b/packages/core_old/src/api/cluster/update.rs deleted file mode 100644 index 5eadc291..00000000 --- a/packages/core_old/src/api/cluster/update.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! Utilities for updating Clusters - -// TODO: requires `store/package.rs` to be implemented. diff --git a/packages/core_old/src/api/handler.rs b/packages/core_old/src/api/handler.rs deleted file mode 100644 index cf7fdff6..00000000 --- a/packages/core_old/src/api/handler.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::path::PathBuf; - -use crate::api::prelude::InternetPayload; -use crate::proxy::send::{send_internet, send_message}; -use onelauncher_utils::io; - -pub async fn parse_uri(cmd: &str) -> crate::Result<()> { - let cmd = handle_cmd(cmd).await?; - send_internet(cmd).await?; - Ok(()) -} - -pub async fn handle_cmd(cmd: &str) -> crate::Result { - tracing::debug!("parsing command {}", &cmd); - - if let Some(sub) = cmd.strip_prefix("onelauncher://") { - Ok(handle_uri(sub).await?) - } else { - let path = io::canonicalize(PathBuf::from(cmd))?; - if let Some(extension) = path.extension() { - if extension == "zip" { - return Ok(InternetPayload::InstallPath { path }); - } - } - send_message(&format!("invalid command filetype {}", path.display())).await?; - Err(anyhow::anyhow!("invalid command filename {}", path.display()).into()) - } -} - -pub async fn handle_uri(sub: &str) -> crate::Result { - Ok(match sub.split_once('/') { - // ://pkg/{id} - installs package id - Some(("pkg", id)) => InternetPayload::InstallPackage { id: id.to_string() }, - // ://pack/{id} - installs pack id - Some(("pack", id)) => InternetPayload::InstallPack { id: id.to_string() }, - // unknown uri command: error - _ => { - send_message(&format!("invalid command {sub}")).await?; - return Err(anyhow::anyhow!("invalid command {sub}").into()); - } - }) -} diff --git a/packages/core_old/src/api/ingress.rs b/packages/core_old/src/api/ingress.rs deleted file mode 100644 index 8ba5fd71..00000000 --- a/packages/core_old/src/api/ingress.rs +++ /dev/null @@ -1,5 +0,0 @@ -use crate::store::{IngressProcessType, IngressProcessor}; - -pub async fn check_ingress_feeds() -> crate::Result { - IngressProcessor::finished(IngressProcessType::IngressFeed).await -} diff --git a/packages/core_old/src/api/java.rs b/packages/core_old/src/api/java.rs deleted file mode 100644 index a16a8e70..00000000 --- a/packages/core_old/src/api/java.rs +++ /dev/null @@ -1,148 +0,0 @@ -use std::path::PathBuf; - -use reqwest::Method; -use serde::{Deserialize, Serialize}; - -use crate::proxy::send::{init_ingress, send_ingress}; -use crate::utils::http::{fetch_advanced, fetch_json}; -use crate::utils::java::{self, get_java_version, JavaVersion}; -use crate::State; -use onelauncher_utils::io::{self, IOError}; - -pub async fn filter_java_version(java_version: Option) -> crate::Result> { - let java = java::locate_java().await?; - Ok(if let Some(java_version) = java_version { - java.into_iter() - .filter(|j| { - let jre_version = get_java_version(&j.version); - jre_version.map_or(false, |jre_version| jre_version.1 == java_version) - }) - .collect() - } else { - java - }) -} - -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Deserialize)] -pub struct JavaZuluPackage { - pub download_url: String, - pub name: PathBuf, - pub java_version: Vec, -} - -pub async fn get_zulu_packages() -> crate::Result> { - let state = State::get().await?; - fetch_json::>( - Method::GET, - format!( - "https://api.azul.com/metadata/v1/zulu/packages/?os={}&arch={}&archive_type=zip&java_package_type=jre&javafx_bundled=false&latest=true&release_status=ga&availability_types=CA&certifications=tck&page=1&page_size=100", - std::env::consts::OS, - std::env::consts::ARCH - ).as_str(), - None, - None, - &state.fetch_semaphore, - ) - .await -} - -// TODO: support more than just zulu ? -#[onelauncher_macros::memory] -pub async fn install_java_from_major(java_version: u32) -> crate::Result { - let packages = get_zulu_packages().await?; - let package = packages - .into_iter() - .find(|p| p.java_version.contains(&java_version)) - .ok_or_else(|| anyhow::anyhow!( - "Could not find a java package for version {}", - java_version - ))?; - - install_java_from_package(package).await -} - -#[onelauncher_macros::memory] -pub async fn install_java_from_package(download: JavaZuluPackage) -> crate::Result { - let state = State::get().await?; - let java_version = *download.java_version.first().unwrap_or(&0); - - let ingress = init_ingress( - crate::IngressType::DownloadJava { - version: java_version, - }, - 100.0, - "downloading java version", - ) - .await?; - - send_ingress(&ingress, 0.0, Some("downloading java version")).await?; - - let file = fetch_advanced( - Method::GET, - &download.download_url, - None, - None, - None, - Some((&ingress, 80.0)), - &state.fetch_semaphore, - ) - .await?; - - let path = state.directories.java_dir().await; - let mut archive = - zip::ZipArchive::new(std::io::Cursor::new(file)).map_err(IOError::from_zip)?; - - if let Some(file) = archive.file_names().next() { - if let Some(dir) = file.split('/').next() { - let path = path.join(dir); - if path.exists() { - io::remove_dir_all(path).await?; - } - } - } - - send_ingress(&ingress, 0.0, Some("extracing java binary")).await?; - archive.extract(&path).map_err(IOError::from_zip)?; - send_ingress(&ingress, 10.0, Some("extracted java binary")).await?; - let mut base_path = path.join( - download - .name - .file_stem() - .unwrap_or_default() - .to_string_lossy() - .to_string(), - ); - - #[cfg(target_os = "macos")] - { - base_path = base_path - .join(format!("zulu-{java_version}.jre")) - .join("Contents") - .join("Home") - .join("bin") - .join("java"); - } - - #[cfg(not(target_os = "macos"))] - { - base_path = base_path.join("bin").join(crate::constants::JAVA_BIN); - } - - send_ingress(&ingress, 100.0, Some("installed java binary")).await?; - - Ok(base_path) -} - -pub async fn check_java(path: PathBuf) -> crate::Result> { - Ok(java::check_java_instance(&path).await) -} - -pub async fn test_java(path: PathBuf, major: u32) -> crate::Result { - let Some(jvm) = java::check_java_instance(&path).await else { - return Ok(false); - }; - - let (maj, _) = get_java_version(&jvm.version)?; - Ok(maj == major) -} \ No newline at end of file diff --git a/packages/core_old/src/api/metadata.rs b/packages/core_old/src/api/metadata.rs deleted file mode 100644 index b9c9c4ff..00000000 --- a/packages/core_old/src/api/metadata.rs +++ /dev/null @@ -1,98 +0,0 @@ -//! Metadata management interface - -use crate::State; -pub use interpulse::api::minecraft::VersionManifest; -pub use interpulse::api::modded::Manifest; - -// TODO: better error handling -// TODO: fix this lmao - -/// Get a [`VersionManifest`] for all available Minecraft versions. -#[tracing::instrument] -pub async fn get_minecraft_versions() -> crate::Result { - let state = State::get().await?; - let meta = state - .metadata - .read() - .await - .minecraft - .clone() - .ok_or_else(|| anyhow::anyhow!("couldn't get minecraft metadata"))?; - - Ok(meta) -} - -/// Get a [`Manifest`] for all available Fabric versions. -#[tracing::instrument] -pub async fn get_fabric_versions() -> crate::Result { - let state = State::get().await?; - let meta = state - .metadata - .read() - .await - .fabric - .clone() - .ok_or_else(|| anyhow::anyhow!("couldn't get fabric metadata"))?; - - Ok(meta) -} - -/// Get a [`Manifest`] for all available Quilt versions. -#[tracing::instrument] -pub async fn get_quilt_versions() -> crate::Result { - let state = State::get().await?; - let meta = state - .metadata - .read() - .await - .quilt - .clone() - .ok_or_else(|| anyhow::anyhow!("couldn't get quilt metadata"))?; - - Ok(meta) -} - -/// Get a [`Manifest`] for all available Forge versions. -#[tracing::instrument] -pub async fn get_forge_versions() -> crate::Result { - let state = State::get().await?; - let meta = state - .metadata - .read() - .await - .forge - .clone() - .ok_or_else(|| anyhow::anyhow!("couldn't get forge metadata"))?; - - Ok(meta) -} - -/// Get a [`Manifest`] for all available NeoForge versions. -#[tracing::instrument] -pub async fn get_neoforge_versions() -> crate::Result { - let state = State::get().await?; - let meta = state - .metadata - .read() - .await - .neoforge - .clone() - .ok_or_else(|| anyhow::anyhow!("couldn't get neoforge metadata"))?; - - Ok(meta) -} - -// /// Get a [`Manifest`] for all available Legacy Fabric versions. -// #[tracing::instrument] -// pub async fn get_legacy_fabric_versions() -> crate::Result { -// let state = State::get().await?; -// let meta = state -// .metadata -// .read() -// .await -// .legacy_fabric -// .clone() -// .ok_or_else(|| anyhow!("missing legacyfabric metadata"))?; - -// Ok(meta) -// } diff --git a/packages/core_old/src/api/minecraft.rs b/packages/core_old/src/api/minecraft.rs deleted file mode 100644 index 25f1a444..00000000 --- a/packages/core_old/src/api/minecraft.rs +++ /dev/null @@ -1,87 +0,0 @@ -//! Authentication flow manager - -use crate::store::{MinecraftCredentials, MinecraftLogin, State}; - -/// Begin a Microsoft authentication flow. -#[tracing::instrument] -pub async fn begin() -> crate::Result { - let state = State::get().await?; - let mut users = state.users.write().await; - - users.begin().await -} - -/// Complete a Microsoft authentication flow to recieve [`MinecraftCredentials`]. -#[tracing::instrument] -pub async fn finish(code: &str, flow: MinecraftLogin) -> crate::Result { - let state = State::get().await?; - let mut users = state.users.write().await; - - users.finish(code, flow).await -} - -/// Get the current default user if it exists by [`uuid::Uuid`]. -#[tracing::instrument] -pub async fn get_default_user() -> crate::Result> { - let state = State::get().await?; - let users = state.users.read().await; - - Ok(users.default_user) -} - -/// Set the current default user by [`uuid::Uuid`]. -#[tracing::instrument] -pub async fn set_default_user(user: Option) -> crate::Result<()> { - let user = match user { - Some(user) => Some(get_user(user).await?.id), - None => None, - }; - - let state = State::get().await?; - let mut users = state.users.write().await; - - users.default_user = user; - users.save().await?; - - Ok(()) -} - -/// Remove a user account by its [`uuid::Uuid`] from the global store. -#[tracing::instrument] -pub async fn remove_user(user: uuid::Uuid) -> crate::Result<()> { - let state = State::get().await?; - let mut users = state.users.write().await; - - users.remove(user).await?; - - if users.default_user == Some(user) { - set_default_user(Some(user)).await?; - } - - Ok(()) -} - -/// Get a list of user [`MinecraftCredentials`]. -#[tracing::instrument] -pub async fn users() -> crate::Result> { - let state = State::get().await?; - let users = state.users.read().await; - - Ok(users.users.values().cloned().collect()) -} - -/// Get a specifc user's [`MinecraftCredentials`] by their [`uuid::Uuid`]. -/// Use [`crate::store::MinecraftState#refresh`] instead. -#[tracing::instrument] -pub async fn get_user(user: uuid::Uuid) -> crate::Result { - let state = State::get().await?; - let users = state.users.read().await; - - let user = users - .users - .get(&user) - .ok_or_else(|| anyhow::anyhow!("failed to get nonexistent user with uuid {user}"))? - .clone(); - - Ok(user) -} diff --git a/packages/core_old/src/api/mod.rs b/packages/core_old/src/api/mod.rs deleted file mode 100644 index b8653854..00000000 --- a/packages/core_old/src/api/mod.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! **`OneLauncher` API** -//! -//! API for interacting with our Rust core. - -pub mod cluster; -pub mod handler; -pub mod ingress; -pub mod java; -pub mod metadata; -pub mod minecraft; -pub mod package; -pub mod processor; -pub mod proxy; -pub mod settings; - -pub mod data { - pub use crate::store::{ - ClusterMeta, Credentials, Directories, InitHooks, JavaOptions, Loader, ManagedDependency, - ManagedPackage, ManagedUser, ManagedVersion, Memory, MinecraftCredentials, PackageData, - PackageType, Resolution, Settings, - }; -} - -pub mod prelude { - pub use crate::cluster::{self, create, Cluster}; - pub use crate::data::*; - pub use crate::proxy::InternetPayload; - pub use crate::store::{ClusterPath, JavaVersions, PackageDependency, PackagePath}; - pub use crate::utils::java::JavaVersion; - pub use crate::{java, metadata, minecraft, package, processor, settings, State}; -} diff --git a/packages/core_old/src/api/package/content/curseforge.rs b/packages/core_old/src/api/package/content/curseforge.rs deleted file mode 100644 index 75c665d9..00000000 --- a/packages/core_old/src/api/package/content/curseforge.rs +++ /dev/null @@ -1,711 +0,0 @@ -use std::collections::HashMap; - -use bytes::Bytes; -use chrono::{DateTime, Utc}; -use reqwest::Method; -use serde::{Deserialize, Serialize}; -use serde_json::json; -use serde_repr::{Deserialize_repr, Serialize_repr}; -use url::Url; - -use crate::data::{Loader, ManagedPackage, ManagedUser, ManagedVersion, PackageType}; -use crate::store::{ManagedVersionFile, ManagedVersionReleaseType, ProviderSearchResults, SearchResult}; -use crate::utils::{http, pagination::Pagination}; -use crate::{Result, State}; - -/// Mapping of Curseforge "classes" to their respective category ids -#[derive(Debug, Default, Serialize_repr, Deserialize_repr)] -#[repr(u32)] -pub enum CFPackageType { - #[default] - Mods = 6, - ModPack = 4471, - DataPack = 6945, - ResourcePack = 12, - ShaderPack = 6552, -} - -impl From for PackageType { - fn from(package_type: CFPackageType) -> Self { - match package_type { - CFPackageType::Mods => PackageType::Mod, - CFPackageType::ModPack => PackageType::ModPack, - CFPackageType::DataPack => PackageType::DataPack, - CFPackageType::ResourcePack => PackageType::ResourcePack, - CFPackageType::ShaderPack => PackageType::ShaderPack, - } - } -} - -impl From for CFPackageType { - fn from(package_type: PackageType) -> Self { - match package_type { - PackageType::Mod => CFPackageType::Mods, - PackageType::ModPack => CFPackageType::ModPack, - PackageType::DataPack => CFPackageType::DataPack, - PackageType::ResourcePack => CFPackageType::ResourcePack, - PackageType::ShaderPack => CFPackageType::ShaderPack, - } - } -} - -/// Mapping of Curseforge supported loaders to their respective id -#[derive(Debug, Default, Serialize_repr, Deserialize_repr)] -#[repr(u8)] -pub enum CurseforgeLoader { - #[default] - Any = 0, - Forge = 1, - Fabric = 4, - Quilt = 5, - NeoForge = 6, -} - -impl From for CurseforgeLoader { - fn from(loader: String) -> Self { - match loader.to_lowercase().as_str() { - "forge" => CurseforgeLoader::Forge, - "fabric" => CurseforgeLoader::Fabric, - "quilt" => CurseforgeLoader::Quilt, - "neoforge" => CurseforgeLoader::NeoForge, - _ => CurseforgeLoader::Any, - } - } -} - -impl From for CurseforgeLoader { - fn from(loader: Loader) -> Self { - match loader { - Loader::Forge => CurseforgeLoader::Forge, - Loader::Fabric => CurseforgeLoader::Fabric, - Loader::Quilt => CurseforgeLoader::Quilt, - Loader::NeoForge => CurseforgeLoader::NeoForge, - _ => CurseforgeLoader::Any, - } - } -} - -impl Into for CurseforgeLoader { - fn into(self) -> Loader { - match self { - CurseforgeLoader::Forge => Loader::Forge, - CurseforgeLoader::Fabric => Loader::Fabric, - CurseforgeLoader::Quilt => Loader::Quilt, - CurseforgeLoader::NeoForge => Loader::NeoForge, - _ => Loader::Vanilla, - } - } -} - -#[derive(Default, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct CurseforgePackage { - pub id: u32, - pub game_id: u32, - #[serde(alias = "classId")] - pub package_type: CFPackageType, - pub name: String, - pub slug: String, - pub links: Links, - pub summary: String, - // pub status: u32, - pub download_count: u64, - #[serde(default)] - pub is_featured: bool, - // pub primary_category_id: u32, - pub categories: Vec, - pub authors: Vec, - pub logo: Option, - // pub screenshots: Vec, - // pub main_file_id: u32, - // pub latest_files: Vec, - // pub latest_files_indexes: Vec, - // pub latest_early_access_files_indexes: Vec, - pub date_created: DateTime, - pub date_modified: DateTime, - pub date_released: DateTime, - // #[serde(default)] - // pub allow_mod_distribution: bool, - // pub game_popularity_rank: u32, - // pub is_available: bool, - pub thumbs_up_count: u32, - pub rating: Option, -} - -impl From for ManagedPackage { - fn from(package: CurseforgePackage) -> Self { - ManagedPackage { - id: package.id.to_string(), - title: package.name, - provider: super::Providers::Curseforge, - package_type: package.package_type.into(), - description: package.summary, - body: crate::store::PackageBody::Url(format!("/v1/mods/{}/description", package.id)), - main: package.slug, - versions: vec![], - game_versions: vec![], - loaders: vec![], - icon_url: package.logo.and_then(|l| Some(l.url)), - created: Some(package.date_created), - updated: Some(package.date_modified), - client: crate::store::PackageSide::Unknown, - server: crate::store::PackageSide::Unknown, - downloads: package.download_count, - followers: package.rating.unwrap_or(0.0) as u32, - categories: vec![], // TODO - optional_categories: None, - license: None, - author: crate::store::Author::Users( - package - .authors - .into_iter() - .map(|a| ManagedUser { - id: a.id.to_string(), - username: a.name, - url: Some(a.url), - is_organization_user: false, - avatar_url: None, - bio: None, - role: None, - }) - .collect(), - ), - is_archived: false, - } - } -} - -impl Into for CurseforgePackage { - fn into(self) -> SearchResult { - SearchResult { - title: self.name, - slug: self.slug, - project_id: self.id.to_string(), - author: self - .authors - .first() - .and_then(|a| Some(a.name.clone())) - .unwrap_or(String::from("Unknown")), - description: self.summary, - client_side: crate::store::PackageSide::Unknown, - server_side: crate::store::PackageSide::Unknown, - project_type: self.package_type.into(), - downloads: self.download_count, - icon_url: self.logo.map_or(String::new(), |l| l.url), - categories: vec![], // TODO - versions: vec![], - follows: self.thumbs_up_count, - date_created: Some(self.date_created), - date_modified: Some(self.date_modified), - } - } -} - -#[derive(Default, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Links { - pub website_url: Option, - pub wiki_url: Option, - pub issues_url: Option, - pub source_url: Option, -} - -#[derive(Default, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Category { - pub id: u32, - pub game_id: u32, - pub name: String, - pub slug: String, - pub url: String, - pub icon_url: String, - pub date_modified: String, - // pub parent_category_id: u32, - // pub display_index: u32, -} - -#[derive(Default, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Author { - pub id: u32, - pub name: String, - pub url: String, -} - -#[derive(Default, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Logo { - pub id: u32, - pub mod_id: u32, - pub title: String, - pub description: String, - pub thumbnail_url: String, - pub url: String, -} - -#[derive(Default, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Screenshot { - pub id: u32, - pub mod_id: u32, - pub title: String, - pub description: String, - pub thumbnail_url: String, - pub url: String, -} - -#[derive(Default, Serialize_repr, Deserialize_repr, Clone, Debug)] -#[repr(u8)] -pub enum ModFileReleaseType { - #[default] - Release = 1, - Beta = 2, - Alpha = 3, -} - -impl Into for ModFileReleaseType { - fn into(self) -> ManagedVersionReleaseType { - match self { - ModFileReleaseType::Release => ManagedVersionReleaseType::Release, - ModFileReleaseType::Beta => ManagedVersionReleaseType::Beta, - ModFileReleaseType::Alpha => ManagedVersionReleaseType::Alpha, - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ModFile { - pub id: u32, - pub game_id: u32, - pub mod_id: u32, - #[serde(default)] - pub is_available: bool, - pub display_name: String, - pub file_name: String, - pub release_type: ModFileReleaseType, - // pub file_status: u32, - pub hashes: Vec, - pub file_date: DateTime, - pub file_length: u64, - pub download_count: u32, - pub file_size_on_disk: Option, - pub download_url: Option, - pub game_versions: Vec, - pub sortable_game_versions: Vec, - pub dependencies: Vec, - // pub expose_as_alternative: bool, - // pub parent_project_file_id: u32, - // pub alternate_file_id: u32, - // pub is_server_pack: bool, - // pub server_pack_file_id: u32, - // pub is_early_access_content: bool, - // pub early_access_end_date: String, - pub file_fingerprint: u32, - // pub modules: Vec, -} - -impl Into for ModFile { - fn into(self) -> ManagedVersion { - let mut hashes = HashMap::new(); - - for hash in self.hashes { - hashes.insert(hash.algo.name(), hash.value); - } - - let mut game_versions = Vec::new(); - let mut loader = CurseforgeLoader::Any; - - for version in self.sortable_game_versions { - if version.game_version.is_empty() { - // This is most likely a loader - loader = CurseforgeLoader::from(version.game_version_name); - continue; - } - - game_versions.push(version.game_version); - } - - let mut files = Vec::new(); - if let Some(download_url) = self.download_url { - files.push(ManagedVersionFile { - url: download_url, - file_name: self.file_name, - primary: true, - size: self.file_size_on_disk.unwrap_or(self.file_length), - file_type: Some(crate::store::PackageFile::RequiredPack), - hashes, - }); - } - - ManagedVersion { - package_id: self.mod_id.to_string(), - id: self.id.to_string(), - name: self.display_name.clone(), - author: String::new(), - loaders: vec![Into::::into(loader)], - changelog: String::new(), - changelog_url: None, - deps: vec![], - downloads: self.download_count, - featured: false, - is_available: self.is_available && files.len() > 0, - files, - game_versions, - published: Some(self.file_date), - version_display: self.display_name, - version_type: self.release_type.into(), - } - } -} - -#[derive(Debug, serde_repr::Serialize_repr, serde_repr::Deserialize_repr)] -#[repr(u8)] -pub enum HashAlgorithm { - Sha1 = 1, - MD5 = 2, -} - -impl HashAlgorithm { - pub fn name(&self) -> String { - match self { - Self::Sha1 => "sha1", - Self::MD5 => "md5", - }.to_string() - } -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Hash { - pub value: String, - pub algo: HashAlgorithm, -} - -#[derive(Default, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SortableGameVersion { - pub game_version_name: String, - pub game_version_padded: String, - pub game_version: String, - pub game_version_release_date: String, - pub game_version_type_id: u32, -} - -#[derive(Default, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Dependency { - pub mod_id: u32, - pub relation_type: u32, -} - -#[derive(Default, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Module { - pub name: String, - pub fingerprint: u32, -} - -#[derive(Default, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ModFilesIndex { - pub game_version: String, - pub file_id: u32, - pub filename: String, - pub release_type: u32, - pub game_version_type_id: u32, - #[serde(default)] - pub mod_loader: CurseforgeLoader, -} - -// https://docs.curseforge.com/rest-api/?shell#tocS_ModsSearchSortField -#[allow(dead_code)] -enum SearchSort { - Featured = 1, - Popularity = 2, - LastUpdated = 3, - Name = 4, - Author = 5, - TotalDownloads = 6, - Rating = 12, -} - -#[derive(Default, Debug, Serialize, Deserialize)] -struct CFData { - data: T, -} - -#[derive(Default, Debug, Serialize, Deserialize)] -struct CFPaginatedData { - pagination: Pagination, - data: T, -} - -pub async fn fetch_advanced( - method: Method, - url: &str, - url_builder: Option, - headers: Option>, - json_body: Option, -) -> Result { - // TODO: Get the API key from settings, fallback to constant, and error if missing - let key = crate::constants::CURSEFORGE_API_KEY; - - let mut headers = headers.unwrap_or(HashMap::<&str, &str>::new()); - headers.insert("x-api-key", key); - - let url = - &mut Url::parse(format!("{}{}", crate::constants::CURSEFORGE_API_URL, url).as_str())?; - - if let Some(url_builder) = url_builder { - url_builder(url); - } - - http::fetch_advanced( - method, - url.as_str(), - None, - json_body, - Some(headers), - None, - &State::get().await?.fetch_semaphore, - ) - .await -} - -pub async fn fetch_url_builder( - url: &str, - url_builder: Option, -) -> Result { - fetch_advanced(Method::GET, url, url_builder, None, None).await -} - -async fn fetch(url: &str) -> Result { - fetch_url_builder(url, None::).await -} - -#[tracing::instrument(skip_all)] -pub async fn search( - query: Option, - limit: Option, - offset: Option, - game_versions: Option>, - _categories: Option>, - loaders: Option>, - package_types: Option>, -) -> Result { - type CFSearchResults = CFPaginatedData>; - - let results: CFSearchResults = serde_json::from_slice( - &fetch_url_builder( - "/v1/mods/search", - Some(Box::new(move |url: &mut Url| { - let params = &mut url.query_pairs_mut(); - - params.append_pair( - "gameId", - crate::constants::CURSEFORGE_GAME_ID.to_string().as_str(), - ); - params.append_pair("pageSize", limit.unwrap_or(10).to_string().as_str()); - params.append_pair("index", offset.unwrap_or(0).to_string().as_str()); - params.append_pair( - "sortField", - (SearchSort::Popularity as u8).to_string().as_str(), - ); - params.append_pair("sortOrder", "desc"); - - if let Some(query) = query { - params.append_pair("searchFilter", query.as_str()); - } - - if let Some(game_versions) = game_versions { - // Need to create a list e.g. ["1.16.5", "1.17.1"] - params.append_pair( - "gameVersions", - format!( - "[{}]", - game_versions - .into_iter() - .map(|v| format!("\"{}\"", v)) - .collect::>() - .join(",") - ) - .as_str(), - ); - } - - if let Some(loaders) = loaders { - params.append_pair( - "modLoaderTypes", - format!( - "[{}]", - loaders - .into_iter() - .map(|l| (CurseforgeLoader::from(l) as u32).to_string()) - .collect::>() - .join(",") - ) - .as_str(), - ); - } - - if let Some(package_types) = package_types { - if package_types.len() == 0 { - return; - } - - if package_types.len() > 1 { - tracing::warn!( - "curseforge provider does not support multiple package types" - ); - } - - let package_type = package_types.first().unwrap(); - - params.append_pair( - "classId", - (CFPackageType::from(*package_type) as u32) - .to_string() - .as_str(), - ); - } - })), - ) - .await?, - )?; - - Ok(ProviderSearchResults { - total: results.pagination.total_count, - results: results - .data - .into_iter() - .map(Into::into) - .collect::>(), - provider: crate::package::content::Providers::Curseforge, - }) -} - -pub async fn get(id: u32) -> Result { - Ok( - serde_json::from_slice::>(&fetch(format!("/v1/mods/{}", id).as_str()).await?)? - .data, - ) -} - -pub async fn get_multiple(ids: &[u32]) -> Result> { - let mut map = serde_json::Map::new(); - map.insert("modIds".to_string(), ids.to_vec().into()); - - let json_body = serde_json::Value::Object(map); - - Ok( - serde_json::from_slice::>( - &fetch_advanced( - Method::POST, - "/v1/mods", - None::, - None, - Some(json_body), - ).await?)? - .data, - ) -} - -pub async fn get_package_body(url: String) -> Result { - Ok(serde_json::from_slice::>(&fetch(&url).await?)?.data) -} - -pub async fn get_all_versions( - project_id: &str, - game_versions: Option>, - loaders: Option>, - page: Option, - page_size: Option, -) -> Result<(Vec, Pagination)> { - let data: CFPaginatedData> = serde_json::from_slice::>( - &fetch_url_builder( - format!("/v1/mods/{project_id}/files").as_str(), - Some(Box::new(move |url: &mut Url| { - if let Some(game_versions) = game_versions { - if let Some(version) = game_versions.first() { - url.query_pairs_mut().append_pair("gameVersion", version); - } - } - - if let Some(loader) = loaders { - if let Some(loader) = loader.first() { - url.query_pairs_mut().append_pair( - "modLoaderType", - (CurseforgeLoader::from(*loader) as u8).to_string().as_str(), - ); - } - } - - let page = page.unwrap_or(0); - url.query_pairs_mut().append_pair("index", page.to_string().as_str()); - - let page_size = page_size.unwrap_or(10); - url.query_pairs_mut().append_pair("pageSize", page_size.to_string().as_str()); - })), - ) - .await?, - )?; - - Ok((data.data, data.pagination)) -} - -pub async fn get_versions( - versions: Vec, -) -> Result> { - let json_body = json!({ - "fileIds": versions.iter().map(|v| v.parse::().unwrap()).collect::>() - }); - - Ok(serde_json::from_slice::>( - &fetch_advanced( - Method::POST, - format!("/v1/mods/files").as_str(), - None::, - None, - Some(json_body) - ) - .await?, - )? - .data) -} - -pub async fn get_versions_by_hashes( - hashes: Vec, -) -> Result> { - let body = json!({ - "fingerprints": hashes.into_iter().map(|hash| hash.parse::().unwrap_or_default()).collect::>() - }); - - #[derive(Debug, Serialize, Deserialize)] - #[serde(rename_all = "camelCase")] - struct Response { - exact_matches: Vec, - exact_fingerprints: Vec - } - - #[derive(Debug, Serialize, Deserialize)] - struct FingerprintMatch { - id: u32, - file: ModFile - } - - let files = serde_json::from_slice::>( - &fetch_advanced( - Method::POST, - format!("/v1/fingerprints/{}", crate::constants::CURSEFORGE_GAME_ID).as_str(), - None::, - None, - Some(body), - ) - .await?, - )?; - - Ok(files.data.exact_matches.into_iter().map(|m| (m.file.file_fingerprint.to_string(), m.file)).collect()) -} - - diff --git a/packages/core_old/src/api/package/content/mod.rs b/packages/core_old/src/api/package/content/mod.rs deleted file mode 100644 index ede86dbd..00000000 --- a/packages/core_old/src/api/package/content/mod.rs +++ /dev/null @@ -1,337 +0,0 @@ -//! **`OneLauncher` Content Package** -//! -//! Utilities for searching and downloading content packages to `OneLauncher`. - -use std::collections::HashMap; -use std::path::PathBuf; - -use modrinth::{Facet, FacetOperation}; -use reqwest::Method; -use serde::{Deserialize, Serialize}; - -use crate::data::{Loader, ManagedPackage, ManagedUser, ManagedVersion, PackageType}; -use crate::package::content::modrinth::FacetBuilder; -use crate::store::{Author, PackageBody, ProviderSearchResults}; -use crate::utils::crypto; -use crate::utils::http::fetch_json; -use crate::utils::pagination::Pagination; -use crate::{Result, State}; - -mod curseforge; -mod modrinth; -mod skyclient; - -/// Providers for content packages -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)] -pub enum Providers { - Modrinth, - Curseforge, - SkyClient, -} - -impl std::fmt::Display for Providers { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.name()) - } -} - -impl Providers { - /// Get the name of the provider - #[must_use] - pub const fn name(&self) -> &str { - match self { - Self::Modrinth => "Modrinth", - Self::Curseforge => "Curseforge", - Self::SkyClient => "SkyClient", - } - } - - /// Get the URL of the provider - #[must_use] - pub const fn url(&self) -> &str { - match self { - Self::Modrinth => "https://modrinth.com", - Self::Curseforge => "https://curseforge.com", - Self::SkyClient => "https://skyclient.co", - } - } - - #[must_use] - pub const fn get_providers() -> &'static [Providers] { - &[Self::Modrinth, Self::Curseforge, Self::SkyClient] - } - - #[allow(clippy::too_many_arguments)] - pub async fn search( - &self, - query: Option, - limit: Option, - offset: Option, - game_versions: Option>, - categories: Option>, - loaders: Option>, - package_types: Option>, - open_source: Option, - ) -> Result { - match self { - Self::Modrinth => { - modrinth::search( - query, - limit, - offset, - Some(|mut builder| { - build_facets( - &mut builder, - categories, - game_versions, - loaders, - package_types, - open_source, - ) - }), - ) - .await - } - Self::Curseforge => { - curseforge::search( - query, - limit, - offset, - game_versions, - categories, - loaders, - package_types, - ) - .await - } - Self::SkyClient => { - skyclient::search( - query, - limit, - offset, - game_versions, - loaders - ).await - }, - } - } - - pub async fn get(&self, slug_or_id: &str) -> Result { - Ok(match self { - Self::Modrinth => modrinth::get(slug_or_id).await?.into(), - Self::Curseforge => curseforge::get(slug_or_id.parse::().map_err(|err| anyhow::anyhow!(err))?).await?.into(), - Self::SkyClient => skyclient::get(slug_or_id).await?.into(), - }) - } - - pub async fn get_multiple(&self, slug_or_ids: &[String]) -> Result> { - if slug_or_ids.is_empty() { - return Ok(vec![]); - } - - Ok(match self { - Self::Modrinth => modrinth::get_multiple(slug_or_ids) - .await? - .into_iter() - .map(Into::into) - .collect(), - Self::Curseforge => { - let parsed_ids = slug_or_ids - .iter() - .filter_map(|id| id.parse::().ok()) - .collect::>(); - - if parsed_ids.is_empty() { - return Ok(vec![]); - } - - let parsed_ids = parsed_ids.as_slice(); - - curseforge::get_multiple(parsed_ids) - .await? - .into_iter() - .map(Into::into) - .collect() - }, - Self::SkyClient => skyclient::get_multiple(slug_or_ids) - .await? - .into_iter() - .map(Into::into) - .collect(), - }) - } - - pub async fn get_all_versions( - &self, - project_id: &str, - game_versions: Option>, - loaders: Option>, - page: Option, - page_size: Option, - ) -> Result<(Vec, Pagination)> { - Ok(match self { - Self::Modrinth => { - let data = modrinth::get_all_versions(project_id, game_versions, loaders, page, page_size).await?; - (data.0.into_iter().map(Into::into).collect(), data.1) - } - - Self::Curseforge => { - let data = curseforge::get_all_versions(project_id, game_versions, loaders, page, page_size).await?; - (data.0.into_iter().map(Into::into).collect(), data.1) - } - - Self::SkyClient => { - let data = skyclient::get_all_versions(project_id, game_versions, loaders, page, page_size).await?; - (data.0, data.1) - }, - }) - } - - pub async fn get_versions(&self, versions: Vec) -> Result> { - Ok(match self { - Self::Modrinth => modrinth::get_versions(versions) - .await? - .into_iter() - .map(Into::into) - .collect(), - Self::Curseforge => curseforge::get_versions(versions) - .await? - .into_iter() - .map(Into::into) - .collect(), - Self::SkyClient => skyclient::get_versions(versions).await?, - }) - } - - pub async fn get_authors(&self, author: &Author) -> Result> { - Ok(match author { - Author::Team { .. } => match self { - Self::Modrinth => modrinth::get_authors(author).await?, - _ => return Err(anyhow::anyhow!("{} does not support teams", self).into()), - }, - Author::Users(users) => users.to_owned(), - }) - } - - pub async fn get_package_body(&self, body: &PackageBody) -> Result { - Ok(match body { - PackageBody::Url(url) => match self { - Self::Curseforge => curseforge::get_package_body(url.to_owned()).await?, - _ => return Err(anyhow::anyhow!("{} does not support direct URLs", self).into()), - }, - PackageBody::Markdown(markdown) => markdown.to_owned(), - }) - } - - pub async fn get_versions_by_hashes(&self, hashes: Vec) -> Result> { - Ok(match self { - Self::Modrinth => modrinth::get_versions_by_hashes(hashes) - .await? - .into_iter() - .map(|(hash, version)| (hash, version.into())) - .collect(), - Self::Curseforge => curseforge::get_versions_by_hashes(hashes) - .await? - .into_iter() - .map(|(hash, version)| (hash, version.into())) - .collect(), - Self::SkyClient => skyclient::get_versions_by_hashes(hashes).await?, - }) - } - - pub fn hash_file(&self, path: &PathBuf) -> Result { - match self { - Providers::Modrinth => crypto::sha1_file(path), - Providers::SkyClient => crypto::md5_file(path), - Providers::Curseforge => { - let hash = crypto::murmur2_file(path)?; - Ok(hash.to_string()) - }, - } - } -} - -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Debug, Serialize, Deserialize)] -pub struct FeaturedPackage { - pub package_type: PackageType, - pub provider: Providers, - pub id: String, - pub title: String, - pub description: String, - pub thumbnail: String, - pub oneconfig: bool, -} - -pub async fn get_featured_packages() -> Result> { - let state = State::get().await?; - fetch_json( - Method::GET, - crate::constants::FEATURED_PACKAGES_URL, - None, - None, - &state.fetch_semaphore - ).await -} - -fn build_facets( - builder: &mut FacetBuilder, - categories: Option>, - game_versions: Option>, - loaders: Option>, - package_types: Option>, - open_source: Option, -) -> String { - if let Some(categories) = categories { - for category in categories { - builder.and(Facet( - "categories".to_string(), - FacetOperation::Eq, - category, - )); - } - } - - if let Some(game_versions) = game_versions { - for version in game_versions { - builder.and(Facet("versions".to_string(), FacetOperation::Eq, version)); - } - } - - if let Some(package_types) = package_types { - if package_types.contains(&PackageType::Mod) - || package_types.contains(&PackageType::ModPack) - { - if let Some(loaders) = loaders { - for loader in loaders { - builder.and(Facet( - "categories".to_string(), - FacetOperation::Eq, - loader.to_string(), - )); - } - } - } - - for package_type in package_types { - builder.and(Facet( - "project_types".to_string(), - FacetOperation::Eq, - package_type.get_name().to_string(), - )); - } - } - - if let Some(open_source) = open_source { - builder.and(Facet( - "open_source".to_string(), - FacetOperation::Eq, - open_source.to_string(), - )); - } - - // builder.and(Facet("client_side".to_string(), FacetOperation::Eq, "")) // TODO: Possibly make this client_side = required - - builder.build() -} diff --git a/packages/core_old/src/api/package/content/modrinth.rs b/packages/core_old/src/api/package/content/modrinth.rs deleted file mode 100644 index cf8d8906..00000000 --- a/packages/core_old/src/api/package/content/modrinth.rs +++ /dev/null @@ -1,535 +0,0 @@ -use std::collections::HashMap; -use std::fmt::{Display, Formatter}; - -use crate::data::{Loader, ManagedPackage, ManagedUser, ManagedVersion, PackageType}; -use crate::store::{ - Author, License, ManagedVersionFile, ManagedVersionReleaseType, PackageFile, PackageSide, ProviderSearchResults, SearchResult -}; -use crate::utils::http::{fetch, fetch_advanced}; -use crate::utils::pagination::Pagination; -use crate::{Result, State}; - -use chrono::{DateTime, Utc}; -use reqwest::Method; -use serde::{Deserialize, Serialize}; -use serde_json::Value; - -use super::Providers; - -#[serde_with::serde_as] -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct ModrinthPackage { - pub slug: String, - pub title: String, - #[serde(default)] - pub description: String, - #[serde(default)] - pub categories: Vec, - pub client_side: PackageSide, - pub server_side: PackageSide, - #[serde(default)] - pub body: String, - #[serde(default)] - #[serde(alias = "display_categories")] - pub additional_categories: Vec, - // pub issues_url: String, - // pub source_url: String, - // pub wiki_url: String, - // pub discord_url: String, - // pub donation_urls: Vec, - pub project_type: PackageType, - pub downloads: u32, - pub icon_url: Option, - #[serde(alias = "project_id")] - pub id: String, - pub team: String, - #[serde(default)] - pub organization: Option, - #[serde(alias = "date_created")] - pub published: DateTime, - #[serde(alias = "date_modified")] - pub updated: DateTime, - pub followers: u32, - #[serde(default)] - pub versions: Vec, - pub game_versions: Vec, - #[serde_as(as = "serde_with::VecSkipError<_>")] - pub loaders: Vec, - #[serde(default)] - pub license: Option, - // #[serde(default)] - // pub gallery: Vec, - #[serde(default)] - pub status: String, -} - -impl From for ManagedPackage { - fn from(value: ModrinthPackage) -> Self { - Self { - provider: Providers::Modrinth, - id: value.id, - title: value.title, - description: value.description, - body: crate::store::PackageBody::Markdown(value.body), - main: value.slug, - versions: value.versions, - game_versions: value.game_versions, - loaders: value.loaders, - icon_url: value.icon_url, - created: Some(value.published), - updated: Some(value.updated), - client: value.client_side, - server: value.server_side, - downloads: value.downloads as u64, - followers: value.followers, - categories: value.categories, - optional_categories: Some(value.additional_categories), - package_type: PackageType::Mod, - license: value.license, - author: Author::Team { - team: value.team, - organization: value.organization, - }, - is_archived: value.status == "archived", - } - } -} - -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct DonationUrl { - pub id: String, - pub platform: String, - pub url: String, -} - -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct Gallery { - pub url: String, - pub featured: bool, - #[serde(default)] - pub title: String, - #[serde(default)] - pub description: String, - pub created: DateTime, - #[serde(default)] - pub ordering: i64, -} - -#[derive(Deserialize)] -struct SearchResults { - hits: Vec, - total_hits: u32, -} - -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct ModrinthVersion { - pub game_versions: Vec, - pub loaders: Vec, - pub id: String, - pub project_id: String, - pub author_id: String, - pub featured: bool, - pub name: String, - pub version_number: String, - pub changelog: String, - pub changelog_url: Option, - pub date_published: DateTime, - pub downloads: u32, - pub version_type: String, - pub status: String, - pub requested_status: Value, - pub files: Vec, - pub dependencies: Vec, -} - -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct File { - pub url: String, - pub filename: String, - pub primary: bool, - pub size: u64, - pub file_type: Option, - pub hashes: HashMap -} - -impl From for ManagedVersionFile { - fn from(value: File) -> Self { - Self { - url: value.url, - file_name: value.filename, - primary: value.primary, - size: value.size, - file_type: value.file_type, - hashes: value.hashes - } - } -} - -impl From for ManagedVersion { - fn from(value: ModrinthVersion) -> Self { - Self { - id: value.id, - package_id: value.project_id, - author: value.author_id, - name: value.name, - - featured: value.featured, - version_display: value.version_number, - changelog: value.changelog, - changelog_url: value.changelog_url, - - published: Some(value.date_published), - downloads: value.downloads, - version_type: ManagedVersionReleaseType::from(value.version_type), - - files: value.files.into_iter().map(Into::into).collect(), - is_available: true, - deps: vec![], // TODO [`ManagedDependency`]? - game_versions: value.game_versions, - loaders: value - .loaders - .into_iter() - .filter_map(|loader| Loader::try_from(loader).ok()) - .collect(), - } - } -} - -#[allow(dead_code)] -pub enum FacetOperation { - NotEq, - LargeEq, - Large, - SmallEq, - Small, - Eq, -} - -impl Display for FacetOperation { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { - Self::NotEq => "!=", - Self::LargeEq => ">=", - Self::Large => ">", - Self::SmallEq => "<=", - Self::Small => "<", - Self::Eq => "=", - }) - } -} - -pub struct Facet(pub String, pub FacetOperation, pub String); - -impl Display for Facet { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(format!("{}{}{}", self.0, self.1, self.2).as_str()) - } -} - -pub struct FacetBuilder { - pub facets: Vec>, -} - -impl FacetBuilder { - pub const fn builder() -> Self { - Self { facets: vec![] } - } - - pub fn and(&mut self, facet: Facet) -> &Self { - self.facets.push(vec![facet]); - self - } - - #[allow(dead_code)] - pub fn or(&mut self, facet: Facet) -> &Self { - let mut last_facet = self.facets.pop().unwrap_or_default(); - last_facet.push(facet); - self.facets.push(last_facet); - self - } - - pub fn build(&self) -> String { - let mut builder: Vec = vec![]; - - for facet in &self.facets { - let mut stringified = String::new(); - stringified.push('['); - for (i, f) in facet.iter().enumerate() { - if i != 0 { - stringified.push(','); - } - stringified.push_str(format!("\"{f}\"").as_str()); - } - stringified.push(']'); - - builder.push(stringified); - } - - builder.join(",") - } -} - -macro_rules! format_url { - ($($arg:tt)*) => {{ - format!("{}{}", crate::constants::MODRINTH_API_URL, format!($($arg)*)) - }}; -} - -macro_rules! format_url_v3 { - ($($arg:tt)*) => {{ - format!("{}{}", crate::constants::MODRINTH_V3_API_URL, format!($($arg)*)) - }}; -} - -pub async fn get(id: &str) -> Result { - Ok(serde_json::from_slice( - &fetch( - format_url!("/project/{}", id).as_str(), - None, - &State::get().await?.fetch_semaphore, - ) - .await?, - )?) -} - -pub async fn get_multiple(ids: &[String]) -> Result> { - Ok(serde_json::from_slice( - &fetch( - format_url!( - "/projects?ids=[{}]", - ids.iter() - .map(|id| format!("\"{id}\"")) - .collect::>() - .join(",") - ) - .as_str(), - None, - &State::get().await?.fetch_semaphore, - ) - .await?, - )?) -} - -pub async fn search( - query: Option, - limit: Option, - offset: Option, - facets: Option, -) -> Result -where - F: FnOnce(FacetBuilder) -> String, -{ - let facets = facets.map_or(String::new(), |func| func(FacetBuilder::builder())); - - let results: SearchResults = serde_json::from_slice( - &fetch( - format_url!( - "/search?query={}&limit={}&offset={}{}", - query.unwrap_or_default(), - limit.unwrap_or(10), - offset.unwrap_or(0), - if facets.is_empty() { - String::new() - } else { - format!("&facets=[{facets}]") - } - ) - .as_str(), - None, - &State::get().await?.fetch_semaphore, - ) - .await?, - )?; - - Ok(ProviderSearchResults { - provider: Providers::Modrinth, - results: results.hits, - total: results.total_hits, - }) -} - -#[derive(Deserialize)] -struct TeamMember { - #[serde(default)] - pub role: Option, - pub user: ManagedUser, -} - -/// Get the authors of a project -pub async fn get_authors(author: &Author) -> Result> { - match author { - Author::Users(users) => Ok(users.clone()), - Author::Team { team, organization } => { - if let Some(organization) = organization { - #[derive(Deserialize)] - struct Organization { - pub id: String, - pub name: String, - pub icon_url: String, - pub description: String, - pub members: Vec, - } - - let organization: Organization = serde_json::from_slice( - &fetch( - format_url_v3!("/organization/{}", organization).as_str(), - None, - &State::get().await?.fetch_semaphore, - ) - .await?, - )?; - - let mut org_user = ManagedUser { - id: organization.id, - username: organization.name, - avatar_url: Some(organization.icon_url), - bio: Some(organization.description), - url: None, - is_organization_user: true, - role: None, - }; - - org_user.url = Some(format!("https://modrinth.com/organization/{}", org_user.id)); - - let members = get_team(team.clone()).await; - let members = match members { - Ok(members) => members, - Err(err) => { - tracing::error!("Failed to get team members: {}", err); - organization - .members - .into_iter() - .map(|member| member.user) - .collect::>() - } - }; - - let mut authors = vec![org_user]; - authors.extend(members); - - Ok(authors) - } else { - get_team(team.clone()).await - } - } - } -} - -async fn get_team(team_id: String) -> Result> { - let authors: Vec = serde_json::from_slice( - &fetch( - format_url!("/team/{}/members", team_id).as_str(), - None, - &State::get().await?.fetch_semaphore, - ) - .await?, - )?; - - Ok(authors - .into_iter() - .map(|member| { - let mut user = member.user; - user.url = Some(format!("https://modrinth.com/user/{}", user.id)); - user.role = member.role; - user - }) - .collect()) -} - -// TODO: modrinth api v3 -// pub async fn get_org_projects(organization: &str) -> Result<> - -pub async fn get_all_versions( - project_id: &str, - game_versions: Option>, - loaders: Option>, - page: Option, - page_size: Option, -) -> Result<(Vec, Pagination)> { - let mut url = url::Url::parse(format_url!("/project/{}/version", project_id).as_str())?; - if let Some(game_versions) = game_versions { - url.query_pairs_mut().append_pair( - "game_versions", - &game_versions - .iter() - .map(|v| format!("\"{v}\"")) - .collect::>() - .join(","), - ); - } - - if let Some(loaders) = loaders { - url.query_pairs_mut().append_pair( - "loaders", - format!( - "[{}]", - &loaders - .iter() - .map(|l| format!("\"{l}\"")) - .collect::>() - .join(",") - ) - .as_str(), - ); - } - - let data = serde_json::from_slice::>( - &fetch( - format_url!("/project/{}/version", project_id).as_str(), - None, - &State::get().await?.fetch_semaphore, - ) - .await?, - )?; - - let data_len = data.len() as u32; - - Ok(( - data, - Pagination { - index: page.unwrap_or(1), - page_size: page_size.unwrap_or(10), - result_count: data_len, - total_count: data_len, - }, - )) -} - -pub async fn get_versions(versions: Vec) -> Result> { - Ok(serde_json::from_slice( - &fetch( - format_url!( - "/versions?ids=[{}]", - versions - .iter() - .map(|v| format!("\"{v}\"")) - .collect::>() - .join(",") - ) - .as_str(), - None, - &State::get().await?.fetch_semaphore, - ) - .await?, - )?) -} - -pub async fn get_versions_by_hashes(hashes: Vec) -> Result> { - let body = serde_json::json!({ - "hashes": hashes, - "algorithm": "sha1" - }); - - Ok(serde_json::from_slice( - &fetch_advanced( - Method::POST, - format_url!("/version_files").as_str(), - None, - Some(body), - None, - None, - &State::get().await?.fetch_semaphore, - ) - .await? - )?) -} \ No newline at end of file diff --git a/packages/core_old/src/api/package/content/skyclient.rs b/packages/core_old/src/api/package/content/skyclient.rs deleted file mode 100644 index 4c8954d2..00000000 --- a/packages/core_old/src/api/package/content/skyclient.rs +++ /dev/null @@ -1,363 +0,0 @@ -use std::{collections::HashMap, sync::Arc}; - -use chrono::{DateTime, Utc}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use tokio::sync::{OnceCell, RwLock}; - -use crate::{data::{Loader, ManagedPackage, ManagedUser, ManagedVersion}, store::{ManagedVersionFile, ProviderSearchResults, SearchResult}, utils::{http, pagination::Pagination}, Result, State}; - -async fn fetch(url: &str) -> Result { - let state = State::get().await?; - Ok(serde_json::from_slice( - &http::fetch( - format!("{}/{}", crate::constants::SKYCLIENT_BASE_URL, url).as_str(), - None, - &state.fetch_semaphore - ).await? - )?) -} - -static SKYCLIENTSTORE_STATIC: OnceCell> = OnceCell::const_new(); - -struct SkyClientStore { - pub mods: Option> -} - -impl SkyClientStore { - pub async fn get() -> Result>> { - Ok(Arc::new( - SKYCLIENTSTORE_STATIC - .get_or_try_init(Self::initialize) - .await? - .read() - .await, - )) - } - - #[tracing::instrument] - #[onelauncher_macros::memory] - async fn initialize() -> Result> { - let mods: Vec = fetch("/mods/mods.json").await?; - - Ok(RwLock::new(SkyClientStore { - mods: Some(mods) - })) - } -} - - -#[derive(Default, Debug, Clone, Serialize, Deserialize)] -pub struct SkyClientMod { - pub id: String, - #[serde(default)] - pub nicknames: Vec, - pub display: String, - pub creator: String, - pub discord_code: Option, - pub description: Option, - pub icon: Option, - #[serde(default)] - pub links: Vec, - pub versions: Vec, - #[serde(default)] - pub commands: Vec, - pub icon_scaling: Option, - pub oneconfig: Option, -} - -impl Into for SkyClientMod { - fn into(self) -> SearchResult { - SearchResult { - slug: self.id.clone(), - project_id: self.id, - author: self.creator, - categories: vec![], - title: self.display, - client_side: crate::store::PackageSide::Required, - server_side: crate::store::PackageSide::Unknown, - date_created: None, - date_modified: None, - description: self.description.unwrap_or_default(), - project_type: crate::data::PackageType::Mod, - downloads: 0, - icon_url: self.icon.map(|i| format!("{}/icons/{}", crate::constants::SKYCLIENT_BASE_URL, i)).unwrap_or_default(), - versions: self.versions.iter().map(|v| v.version.clone()).collect(), - follows: 0, - } - } -} - -impl Into for SkyClientMod { - fn into(self) -> ManagedPackage { - ManagedPackage { - id: self.id.clone(), - main: self.id, - title: self.display, - body: crate::store::PackageBody::Markdown(self.description.clone().unwrap_or_default()), - categories: vec![], - client: crate::store::PackageSide::Required, - server: crate::store::PackageSide::Unknown, - created: None, // TODO: Get the oldest date from versions - updated: None, // TODO: Get the newest date from versions - description: self.description.unwrap_or_default(), - downloads: 0, - followers: 0, - game_versions: self.versions.iter().flat_map(|v| v.game_versions.clone()).collect(), - icon_url: self.icon.map(|i| format!("{}/icons/{}", crate::constants::SKYCLIENT_BASE_URL, i)), - is_archived: false, - license: None, - loaders: self.versions.iter().flat_map(|v| v.loaders.clone().into_iter().map(Loader::from_string)).collect(), - versions: self.versions.iter().map(|v| v.version.clone()).collect(), - optional_categories: None, - author: crate::store::Author::Users(vec![ - ManagedUser { - id: self.creator.clone(), - username: self.creator, - is_organization_user: false, - avatar_url: None, - bio: None, - role: None, - url: self.discord_code.map(|code| format!("https://discord.gg/{}", code)), - } - ]), - package_type: crate::data::PackageType::Mod, - provider: super::Providers::SkyClient, - } - } -} - -#[derive(Default, Debug, Clone, Serialize, Deserialize)] -pub struct SkyClientModLink { - pub icon: String, - pub text: String, - pub link: String, -} - -#[derive(Default, Debug, Clone, Serialize, Deserialize)] -pub struct SkyClientModVersion { - pub version: String, - pub game_versions: Vec, - pub loaders: Vec, - pub file: String, - pub url: String, - pub hash: String, - pub date_published: DateTime, - pub mod_id: Option, -} - -fn get_managed_version(m: &SkyClientMod, v: &SkyClientModVersion) -> ManagedVersion { - let mut map = HashMap::new(); - map.insert(String::from("md5"), v.hash.clone()); - - ManagedVersion { - id: v.version.clone(), - package_id: m.id.clone(), - author: m.creator.clone(), - changelog_url: None, - changelog: String::new(), - deps: vec![], - downloads: 0, - featured: false, - files: vec![ManagedVersionFile { - file_name: v.file.clone(), - url: v.url.clone(), - file_type: None, - hashes: map, - primary: true, - size: 0, - }], - game_versions: v.game_versions.clone(), - is_available: true, - loaders: v.loaders.iter().map(|l| Loader::from_string(l.to_owned())).collect(), - name: format!("{} v{}", m.display, v.version), - published: Some(v.date_published), - version_display: v.version.clone(), - version_type: crate::store::ManagedVersionReleaseType::Release, - } -} - -pub async fn get(id: &str) -> Result { - let store = SkyClientStore::get().await?; - - let Some(mods) = &store.mods else { - return Err(anyhow::anyhow!("mod not found").into()) - }; - - for m in mods { - if m.id == id { - return Ok(m.clone()); - } - } - - Err(anyhow::anyhow!("mod not found").into()) -} - -pub async fn get_multiple(slug_or_ids: &[String]) -> Result> { - let store = SkyClientStore::get().await?; - - let mods = match &store.mods { - Some(mods) => mods, - None => return Ok(vec![]) - }; - - let mut results = vec![]; - - for id in slug_or_ids { - for m in mods { - if m.id == *id { - results.push(m.clone()); - } - } - } - - Ok(results) -} - -pub async fn get_all_versions( - project_id: &str, - game_versions: Option>, - loaders: Option>, - page: Option, - page_size: Option, -) -> Result<(Vec, Pagination)> { - let mut pagination = Pagination::default(); - let mut versions = vec![]; - - if let Ok(m) = &get(project_id).await { - for v in &m.versions { - let mut can_add = true; - - if let Some(game_versions) = &game_versions { - if !game_versions.is_empty() { - can_add = v.game_versions.iter().any(|gv| game_versions.contains(gv)) - } - } - - if can_add { - if let Some(loaders) = &loaders { - if !loaders.is_empty() { - can_add = v.loaders.iter().any(|l| loaders.contains(&Loader::from_string(l.to_owned()))) - } - } - } - - if can_add { - if page_size.map(|ps| versions.len() < ps as usize).unwrap_or(true) && page.map(|p| p <= pagination.index).unwrap_or(true) { - versions.push(get_managed_version(m, v)); - } - - pagination.total_count += 1; - } - } - } - - Ok((versions, pagination)) -} - -pub async fn get_versions(versions: Vec) -> Result> { - let store = SkyClientStore::get().await?; - - let mods = match &store.mods { - Some(mods) => mods, - None => return Ok(vec![]) - }; - - let mut results = vec![]; - - for m in mods { - for v in &m.versions { - if versions.contains(&v.version) { - results.push(get_managed_version(m, v)); - } - } - } - - Ok(results) -} - -pub async fn get_versions_by_hashes(hashes: Vec) -> Result> { - let store = SkyClientStore::get().await?; - - let mods = match &store.mods { - Some(mods) => mods, - None => return Ok(HashMap::new()) - }; - - let mut results = HashMap::new(); - - for m in mods { - for v in &m.versions { - if hashes.contains(&v.hash) { - results.insert(v.hash.clone(), get_managed_version(m, v)); - } - } - } - - Ok(results) -} - -pub async fn search( - query: Option, - limit: Option, - offset: Option, - game_versions: Option>, - // package_types: Option>, - loaders: Option>, -) -> Result { - let store = SkyClientStore::get().await?; - - let mut results = ProviderSearchResults { - provider: super::Providers::SkyClient, - results: vec![], - total: 0, - }; - - let mods = match &store.mods { - Some(mods) => mods, - None => return Ok(results), - }; - - let query = query.map(|q| q.trim().to_lowercase()); - let limit = limit.map(|l| l as usize); - let offset = offset.unwrap_or(0); - - for m in mods { - let mut can_add = true; - - if let Some(query) = &query { - if !query.is_empty() { - let display = m.display.to_lowercase(); - - can_add = display.contains(query) || m.id.contains(query) || m.nicknames.contains(query) - } - } - - if can_add { - if let Some(game_versions) = &game_versions { - if !game_versions.is_empty() { - can_add = m.versions.iter().any(|v| v.game_versions.iter().any(|gv| game_versions.contains(&gv))) - } - } - } - - if can_add { - if let Some(loaders) = &loaders { - if !loaders.is_empty() { - can_add = m.versions.iter().any(|v| v.loaders.iter().any(|l| loaders.contains(&Loader::from_string(l.to_owned())))) - } - } - } - - if can_add { - if limit.map(|l| results.results.len() < l).unwrap_or(true) && offset <= results.total { - let result: SearchResult = m.clone().into(); - results.results.push(result); - } - - results.total += 1; - } - - } - - Ok(results) -} \ No newline at end of file diff --git a/packages/core_old/src/api/package/from.rs b/packages/core_old/src/api/package/from.rs deleted file mode 100644 index fd5eb170..00000000 --- a/packages/core_old/src/api/package/from.rs +++ /dev/null @@ -1,409 +0,0 @@ -//! Install modpacks from different sources. - -use crate::data::{Loader, ManagedPackage, ManagedVersion, PackageData}; -use crate::prelude::ClusterPath; -use crate::proxy::send::{init_ingress, send_ingress}; -use crate::proxy::IngressId; -use crate::store::{ClusterStage, PackageSide}; -use crate::utils::http::{fetch, fetch_advanced, fetch_json, write_icon}; -use crate::{IngressType, InnerPathLinux, State}; -use onelauncher_utils::io; -use reqwest::Method; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::path::PathBuf; - -#[derive(Serialize, Deserialize, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct PackFormat { - pub game: String, - pub format_version: i32, - pub version_id: String, - pub name: String, - pub summary: Option, - pub files: Vec, - pub dependencies: HashMap, -} - -#[derive(Serialize, Deserialize, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct PackFile { - pub path: InnerPathLinux, - pub hashes: HashMap, - pub env: Option>, - pub downloads: Vec, - pub file_size: u32, -} - -#[derive(Serialize, Deserialize, Eq, PartialEq, Hash)] -#[serde(rename_all = "camelCase", from = "String")] -pub enum PackFileHash { - Sha1, - Sha512, - Unknown(String), -} - -impl From for PackFileHash { - fn from(s: String) -> Self { - match s.as_str() { - "sha1" => Self::Sha1, - "sha512" => Self::Sha512, - _ => Self::Unknown(s), - } - } -} - -#[derive(Serialize, Deserialize, Eq, PartialEq, Hash)] -#[serde(rename_all = "camelCase")] -pub enum EnvType { - Client, - Server, -} - -#[derive(Serialize, Deserialize, Clone, Copy, Hash, PartialEq, Eq, Debug)] -pub enum PackDependency { - #[serde(rename = "forge")] - Forge, - - #[serde(rename = "neoforge")] - #[serde(alias = "neo-forge")] - NeoForge, - - #[serde(rename = "fabric-loader")] - FabricLoader, - - #[serde(rename = "quilt-loader")] - QuiltLoader, - - #[serde(rename = "minecraft")] - Minecraft, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase", tag = "type")] -pub enum CreatePackLocation { - /// Create a pack from a Modrinth modpack. - FromModrinth { - package_id: String, - version_id: String, - title: String, - icon_url: Option, - }, - /// Create a pack from a file (importing, importing an mrpack or zip-style modpack) - FromFile { path: PathBuf }, -} - -/// Modpack associated [`Cluster`] wrapper. -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct CreatePackCluster { - /// The name of the cluster, and relative path. - pub name: String, - /// The game version (MC) of the cluster. - pub mc_version: String, - /// The mod [`Loader`] to use (if applicable). - pub mod_loader: Loader, - /// The mod [`Loader`] version to use, set to `latest` or `stable` or the ID of the loader. - /// The default value is `latest` for the latest version of the mod loader. - pub loader_version: Option, - /// The icon of the cluster. - pub icon: Option, - /// The URL of an icon for a cluster. (ONLY USE THIS FOR A TEMPORARY CLUSTER) - pub icon_url: Option, - /// The linked package data (mainly used for modpacks and updating files). - pub package_data: Option, - /// Whether or not the cluster should skip being installed. - pub skip: Option, - /// Whether or not the cluster should skip file watching. - pub skip_watch: Option, -} - -impl Default for CreatePackCluster { - fn default() -> Self { - Self { - name: "Untitled".to_string(), - mc_version: "1.20.4".to_string(), - mod_loader: Loader::Vanilla, - loader_version: None, - icon: None, - icon_url: None, - package_data: None, - skip: Some(true), - skip_watch: Some(false), - } - } -} - -#[derive(Clone)] -pub struct CreatePack { - pub file: bytes::Bytes, - pub description: CreatePackDescription, -} - -#[derive(Clone, Debug)] -pub struct CreatePackDescription { - pub icon: Option, - pub override_title: Option, - pub package_id: Option, - pub version_id: Option, - pub existing_ingress: Option, - pub cluster_path: ClusterPath, -} - -#[must_use] -pub fn get_cluster_from_pack(location: CreatePackLocation) -> CreatePackCluster { - match location { - CreatePackLocation::FromModrinth { - package_id, - version_id, - title, - icon_url, - } => CreatePackCluster { - name: title, - icon_url, - package_data: Some(PackageData { - package_id: Some(package_id), - version_id: Some(version_id), - locked: Some(true), - }), - ..Default::default() - }, - CreatePackLocation::FromFile { path } => { - let file_name = path - .file_stem() - .unwrap_or_default() - .to_string_lossy() - .to_string(); - CreatePackCluster { - name: file_name, - ..Default::default() - } - } - } -} - -#[tracing::instrument] -#[onelauncher_macros::memory] -pub async fn generate_pack_from_version_id( - package_id: String, - version_id: String, - title: String, - icon_url: Option, - cluster_path: ClusterPath, - existing_ingress: Option, -) -> crate::Result { - let state = State::get().await?; - - let ingress = if let Some(ing) = existing_ingress { - send_ingress(&ing, 0.0, Some("downloading pack file")).await?; - ing - } else { - init_ingress( - IngressType::DownloadPack { - cluster_path: cluster_path.full_path().await?, - package_name: title, - icon: icon_url, - package_version: version_id.clone(), - }, - 100.0, - "downloading pack file", - ) - .await? - }; - - send_ingress(&ingress, 0.0, Some("fetching version")).await?; - let version: ManagedVersion = fetch_json( - Method::GET, - &format!( - "{}version/{}", - crate::constants::MODRINTH_API_URL, - version_id - ), - None, - None, - &state.fetch_semaphore, - ) - .await?; - send_ingress(&ingress, 10.0, None).await?; - - let (url, hash) = if let Some(file) = version.files.iter().find(|x| x.primary) { - Some((file.url.clone(), file.hashes.get("sha1"))) - } else { - version - .files - .first() - .map(|file| (file.url.clone(), file.hashes.get("sha1"))) - } - .ok_or_else(|| anyhow::anyhow!("specified version has no files"))?; - - let file = fetch_advanced( - Method::GET, - &url, - hash.map(|x| &**x), - None, - None, - Some((&ingress, 70.0)), - &state.fetch_semaphore, - ) - .await?; - send_ingress(&ingress, 0.0, Some("fetching project metadata")).await?; - - let pkg: ManagedPackage = fetch_json( - Method::GET, - &format!( - "{}project/{}", - crate::constants::MODRINTH_API_URL, - version.package_id - ), - None, - None, - &state.fetch_semaphore, - ) - .await?; - - send_ingress(&ingress, 10.0, Some("Retrieving icon")).await?; - let icon = if let Some(icon_url) = pkg.icon_url { - let state = State::get().await?; - let icon_bytes = fetch(&icon_url, None, &state.fetch_semaphore).await?; - let filename = icon_url.rsplit('/').next(); - - if let Some(filename) = filename { - Some( - write_icon( - filename, - &state.directories.caches_dir().await, - icon_bytes, - &state.io_semaphore, - ) - .await?, - ) - } else { - None - } - } else { - None - }; - send_ingress(&ingress, 10.0, None).await?; - - Ok(CreatePack { - file, - description: CreatePackDescription { - icon, - override_title: None, - package_id: Some(package_id), - version_id: Some(version_id), - existing_ingress: Some(ingress), - cluster_path, - }, - }) -} - -#[tracing::instrument] -#[onelauncher_macros::memory] -pub async fn generate_pack_from_file( - path: PathBuf, - cluster_path: ClusterPath, -) -> crate::Result { - let file = io::read(&path).await?; - Ok(CreatePack { - file: bytes::Bytes::from(file), - description: CreatePackDescription { - icon: None, - override_title: None, - package_id: None, - version_id: None, - existing_ingress: None, - cluster_path, - }, - }) -} - -/// Sets generated cluster attributes to the pack attributes (using `cluster::edit`). -/// This includes the pack name, icon, game version, loader version, and loader. -#[onelauncher_macros::memory] -pub async fn set_cluster_information( - cluster_path: ClusterPath, - description: &CreatePackDescription, - backup_name: &str, - deps: &HashMap, - ignore_lock: bool, -) -> crate::Result<()> { - let mut game_version: Option<&String> = None; - let mut mod_loader = None; - let mut loader_version = None; - - for (key, value) in deps { - match key { - PackDependency::Forge => { - mod_loader = Some(Loader::Forge); - loader_version = Some(value); - } - PackDependency::NeoForge => { - mod_loader = Some(Loader::NeoForge); - loader_version = Some(value); - } - PackDependency::FabricLoader => { - mod_loader = Some(Loader::Fabric); - loader_version = Some(value); - } - PackDependency::QuiltLoader => { - mod_loader = Some(Loader::Quilt); - loader_version = Some(value); - } - PackDependency::Minecraft => game_version = Some(value), - } - } - - let Some(game_version) = game_version else { - return Err(anyhow::anyhow!("pack did not specify Minecraft version").into()); - }; - let mod_loader = mod_loader.unwrap_or(Loader::Vanilla); - let loader_version = if mod_loader == Loader::Vanilla { - None - } else { - crate::cluster::create::get_loader_version( - game_version.clone(), - mod_loader, - loader_version.cloned(), - ) - .await? - }; - - // crate::api::cluster::create::create_cluster( - // description.override_title.clone().unwrap_or_else(|| backup_name.to_string()), - // mc_version, mod_loader, loader_version, icon, icon_url, package_data, skip, skip_watch) - - crate::api::cluster::edit(&cluster_path, |cl| { - cl.meta.name = description - .override_title - .clone() - .unwrap_or_else(|| backup_name.to_string()); - cl.stage = ClusterStage::PackDownloading; - let package_id = description.package_id.clone(); - let version_id = description.version_id.clone(); - - cl.meta.package_data = if package_id.is_some() && version_id.is_some() { - Some(PackageData { - package_id, - version_id, - locked: if ignore_lock { - cl.meta.package_data.as_ref().and_then(|x| x.locked) - } else { - Some(true) - }, - }) - } else { - None - }; - - cl.meta.icon.clone_from(&description.icon); - cl.meta.mc_version.clone_from(game_version); - cl.meta.loader_version.clone_from(&loader_version); - cl.meta.loader = mod_loader; - - async { Ok(()) } - }) - .await?; - - Ok(()) -} diff --git a/packages/core_old/src/api/package/import/atlauncher.rs b/packages/core_old/src/api/package/import/atlauncher.rs deleted file mode 100644 index 095ca023..00000000 --- a/packages/core_old/src/api/package/import/atlauncher.rs +++ /dev/null @@ -1,240 +0,0 @@ -//! Launcher Import: `ATLauncher` -//! Source Code available at - -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::path::PathBuf; - -use crate::package::from::CreatePackDescription; -use crate::package::import::{self, copy_minecraft}; -use crate::package::{self}; -use crate::prelude::{Cluster, ClusterPath, Loader}; -use crate::store::{ClusterStage, PackageData, State}; -use onelauncher_utils::io; - -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ATInstance { - pub id: String, // minecraft version id ie: 1.12.1, not a name - pub launcher: ATLauncher, - pub java_version: ATJavaVersion, -} - -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ATLauncher { - pub name: String, - pub pack: String, - pub version: String, - pub loader_version: ATLauncherLoaderVersion, - - pub managed_project: Option, - pub managed_version: Option, - pub managed_manifest: Option, -} - -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ATJavaVersion { - pub major_version: u8, - pub component: String, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct ATLauncherLoaderVersion { - pub r#type: String, - pub version: String, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ATLauncherManagedProject { - pub id: String, - pub slug: String, - pub project_type: String, - pub team: String, - pub client_side: Option, - pub server_side: Option, - pub categories: Vec, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ATLauncherManagedVersion { - pub id: String, - pub project_id: String, - pub name: String, - pub version_number: String, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct ATLauncherManagedVersionFile { - pub hashes: HashMap, - pub url: String, - pub filename: String, - pub primary: bool, - pub size: u64, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct ATLauncherManagedVersionDependency { - pub project_id: Option, - pub version_id: Option, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct ATLauncherMod { - pub name: String, - pub version: String, - pub file: String, - - pub managed_project: Option, - pub managed_version: Option, -} - -pub async fn is_valid_atlauncher(instance_folder: PathBuf) -> bool { - let instance: String = io::read_to_string(&instance_folder.join("instance.json")) - .await - .unwrap_or(String::new()); - let instance: Result = - serde_json::from_str::(&instance); - if let Err(e) = instance { - tracing::warn!( - "failed to parse instance.json at {}: {}", - instance_folder.display(), - e - ); - false - } else { - true - } -} - -#[tracing::instrument] -#[onelauncher_macros::memory] -pub async fn import_atlauncher( - // The path to the base ATLauncher folder. - atlauncher_base_path: PathBuf, - // The instance folder in atlauncher_base_path - instance_folder: String, - // The path to the importing Cluster - cluster_path: ClusterPath, -) -> crate::Result<()> { - let atlauncher_instance_path = atlauncher_base_path - .join("instances") - .join(instance_folder.clone()); - let atinstance: String = - io::read_to_string(&atlauncher_instance_path.join("instance.json")).await?; - let atinstance: ATInstance = serde_json::from_str::(&atinstance)?; - - // Icon path should be {instance_folder}/instance.png if it exists, - // Another possibility is ATLauncher/configs/images/{safe_pack_name}.png (safe pack name is alphanumeric lowercase) - let icon_path_primary = atlauncher_instance_path.join("instance.png"); - let safe_pack_name = atinstance - .launcher - .pack - .replace(|c: char| !c.is_alphanumeric(), "") - .to_lowercase(); - let icon_path_secondary = atlauncher_base_path - .join("configs") - .join("images") - .join(safe_pack_name + ".png"); - let icon = match (icon_path_primary.exists(), icon_path_secondary.exists()) { - (true, _) => import::cache_icon(icon_path_primary).await?, - (_, true) => import::cache_icon(icon_path_secondary).await?, - _ => None, - }; - - // Create description from instance.cfg - let description = CreatePackDescription { - icon, - override_title: Some(atinstance.launcher.name.clone()), - package_id: None, - version_id: None, - existing_ingress: None, - cluster_path: cluster_path.clone(), - }; - - let backup_name = format!("ATLauncher-{instance_folder}"); - let minecraft_folder = atlauncher_instance_path; - - import_atlauncher_unmanaged( - cluster_path, - minecraft_folder, - backup_name, - description, - atinstance, - ) - .await?; - - Ok(()) -} - -async fn import_atlauncher_unmanaged( - cluster_path: ClusterPath, - minecraft_folder: PathBuf, - backup_name: String, - description: CreatePackDescription, - atinstance: ATInstance, -) -> crate::Result<()> { - let mod_loader = format!( - "\"{}\"", - atinstance.launcher.loader_version.r#type.to_lowercase() - ); - let mod_loader: Loader = serde_json::from_str::(&mod_loader)?; - - let game_version = atinstance.id; - let loader_version = if mod_loader == Loader::Vanilla { - None - } else { - crate::cluster::create::get_loader_version( - game_version.clone(), - mod_loader, - Some(atinstance.launcher.loader_version.version.clone()), - ) - .await? - }; - - crate::api::cluster::edit(&cluster_path, |cl| { - cl.meta.name = description - .override_title - .clone() - .unwrap_or_else(|| backup_name.to_string()); - cl.stage = ClusterStage::PackDownloading; - cl.meta.package_data = Some(PackageData { - package_id: description.package_id.clone(), - version_id: description.version_id.clone(), - locked: Some(description.package_id.is_some() && description.version_id.is_some()), - }); - cl.meta.icon.clone_from(&description.icon); - cl.meta.mc_version.clone_from(&game_version); - cl.meta.loader_version.clone_from(&loader_version); - cl.meta.loader = mod_loader; - - async { Ok(()) } - }) - .await?; - - // moves .minecraft folder over (ie: overrides such as resourcepacks, mods, etc) - let state = State::get().await?; - let ingress = copy_minecraft( - cluster_path.clone(), - minecraft_folder, - &state.io_semaphore, - None, - ) - .await?; - - if let Some(cluster_val) = crate::api::cluster::get(&cluster_path).await? { - crate::game::install_minecraft(&cluster_val, Some(ingress), false).await?; - { - let state = State::get().await?; - let mut watcher = state.watcher.write().await; - Cluster::watch(&cluster_val.get_full_path().await?, &mut watcher).await?; - } - State::sync().await?; - } - Ok(()) -} diff --git a/packages/core_old/src/api/package/import/curseforge.rs b/packages/core_old/src/api/package/import/curseforge.rs deleted file mode 100644 index 225b9d36..00000000 --- a/packages/core_old/src/api/package/import/curseforge.rs +++ /dev/null @@ -1,168 +0,0 @@ -//! Launcher Import: `CurseForge` -//! Closed Source, all this information is datamined unfortunately. - -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; - -use super::{cache_icon, copy_minecraft}; -use crate::prelude::{Cluster, ClusterPath, Loader}; -use crate::store::{ClusterStage, State}; -use crate::utils::http::{fetch, write_icon}; -use onelauncher_utils::io; - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct MinecraftInstance { - pub name: Option, - pub base_mod_loader: Option, - pub profile_image_path: Option, - pub installed_modpack: Option, - /// Minecraft game version. Non-prioritized, use this if Vanilla - pub game_version: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct MinecraftInstanceModLoader { - pub name: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct InstalledModpack { - pub thumbnail_url: Option, -} - -pub async fn is_valid_curseforge(instance_folder: PathBuf) -> bool { - let minecraftinstance: String = - io::read_to_string(&instance_folder.join("minecraftinstance.json")) - .await - .unwrap_or(String::new()); - let minecraftinstance: Result = - serde_json::from_str::(&minecraftinstance); - minecraftinstance.is_ok() -} - -#[allow(clippy::too_many_lines)] -pub async fn import_curseforge( - // The path to the CurseForge instance - curseforge_instance_folder: PathBuf, - // The path to the cluster - cluster_path: ClusterPath, -) -> crate::Result<()> { - let minecraft_instance: String = - io::read_to_string(&curseforge_instance_folder.join("minecraftinstance.json")).await?; - let minecraft_instance: MinecraftInstance = - serde_json::from_str::(&minecraft_instance)?; - let override_title: Option = minecraft_instance.name.clone(); - let backup_name = format!( - "Curseforge-{}", - curseforge_instance_folder - .file_name() - .map_or("Unknown".to_string(), |a| a.to_string_lossy().to_string()) - ); - - let state = State::get().await?; - let mut icon = None; - - if let Some(icon_path) = minecraft_instance.profile_image_path.clone() { - icon = cache_icon(icon_path).await?; - } else if let Some(InstalledModpack { - thumbnail_url: Some(thumbnail_url), - }) = minecraft_instance.installed_modpack.clone() - { - let icon_bytes = fetch(&thumbnail_url, None, &state.fetch_semaphore).await?; - let filename = thumbnail_url.rsplit('/').last(); - if let Some(filename) = filename { - icon = Some( - write_icon( - filename, - &state.directories.caches_dir().await, - icon_bytes, - &state.io_semaphore, - ) - .await?, - ); - } - } - - if let Some(instance_mod_loader) = minecraft_instance.base_mod_loader { - let game_version = minecraft_instance.game_version; - let mut mod_loader = None; - let mut loader_version = None; - - match instance_mod_loader.name.split('-').collect::>()[..] { - ["forge", version] => { - mod_loader = Some(Loader::Forge); - loader_version = Some(version.to_string()); - } - ["fabric", version, _game_version] => { - mod_loader = Some(Loader::Fabric); - loader_version = Some(version.to_string()); - } - _ => {} - } - - let mod_loader = mod_loader.unwrap_or(Loader::Vanilla); - let loader_version = if mod_loader == Loader::Vanilla { - None - } else { - crate::cluster::create::get_loader_version( - game_version.clone(), - mod_loader, - loader_version, - ) - .await? - }; - - crate::api::cluster::edit(&cluster_path, |cl| { - cl.meta.name = override_title - .clone() - .unwrap_or_else(|| backup_name.to_string()); - cl.stage = ClusterStage::PackDownloading; - cl.meta.icon.clone_from(&icon); - cl.meta.mc_version.clone_from(&game_version); - cl.meta.loader_version.clone_from(&loader_version); - cl.meta.loader = mod_loader; - - async { Ok(()) } - }) - .await?; - } else { - crate::api::cluster::edit(&cluster_path, |cl| { - cl.meta.name = override_title - .clone() - .unwrap_or_else(|| backup_name.to_string()); - cl.meta.icon.clone_from(&icon); - cl.meta - .mc_version - .clone_from(&minecraft_instance.game_version); - cl.meta.loader_version = None; - cl.meta.loader = Loader::Vanilla; - - async { Ok(()) } - }) - .await?; - } - - let state = State::get().await?; - let ingress = copy_minecraft( - cluster_path.clone(), - curseforge_instance_folder, - &state.io_semaphore, - None, - ) - .await?; - - if let Some(cluster_val) = crate::api::cluster::get(&cluster_path).await? { - crate::game::install_minecraft(&cluster_val, Some(ingress), false).await?; - { - let state = State::get().await?; - let mut watcher = state.watcher.write().await; - Cluster::watch(&cluster_val.get_full_path().await?, &mut watcher).await?; - } - State::sync().await?; - } - - Ok(()) -} diff --git a/packages/core_old/src/api/package/import/ftb.rs b/packages/core_old/src/api/package/import/ftb.rs deleted file mode 100644 index d842a3a7..00000000 --- a/packages/core_old/src/api/package/import/ftb.rs +++ /dev/null @@ -1,2 +0,0 @@ -//! Launcher Import: FTB App -//! Source Code available at diff --git a/packages/core_old/src/api/package/import/gdlauncher.rs b/packages/core_old/src/api/package/import/gdlauncher.rs deleted file mode 100644 index 377e3b49..00000000 --- a/packages/core_old/src/api/package/import/gdlauncher.rs +++ /dev/null @@ -1,109 +0,0 @@ -//! Launcher Import: `GDLauncher` (legacy) -//! Source Code available at - -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; - -use super::{cache_icon, copy_minecraft}; -use crate::prelude::{Cluster, ClusterPath, Loader}; -use crate::store::{ClusterStage, State}; -use onelauncher_utils::io; - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct GDLauncherConfig { - pub background: Option, - pub loader: GDLauncherLoader, - // pub mods: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct GDLauncherLoader { - pub loader_type: Loader, - pub loader_version: Option, - pub mc_version: String, - pub source: Option, - pub source_name: Option, -} - -pub async fn is_valid_gdlauncher(instance_folder: PathBuf) -> bool { - let config: String = io::read_to_string(&instance_folder.join("config.json")) - .await - .unwrap_or(String::new()); - let config: Result = - serde_json::from_str::(&config); - config.is_ok() -} - -pub async fn import_gdlauncher( - gdlauncher_instance_folder: PathBuf, - cluster_path: ClusterPath, -) -> crate::Result<()> { - let config: String = - io::read_to_string(&gdlauncher_instance_folder.join("config.json")).await?; - let config: GDLauncherConfig = serde_json::from_str::(&config)?; - let override_title: Option = config.loader.source_name.clone(); - let backup_name = format!( - "GDLauncher-{}", - gdlauncher_instance_folder - .file_name() - .map_or("Unknown".to_string(), |a| a.to_string_lossy().to_string()) - ); - - let icon = config - .background - .clone() - .map(|b| gdlauncher_instance_folder.join(b)); - let icon = if let Some(icon) = icon { - cache_icon(icon).await? - } else { - None - }; - - let game_version = config.loader.mc_version; - let mod_loader = config.loader.loader_type; - let loader_version = config.loader.loader_version; - - let loader_version = if mod_loader == Loader::Vanilla { - None - } else { - crate::cluster::create::get_loader_version(game_version.clone(), mod_loader, loader_version) - .await? - }; - - crate::api::cluster::edit(&cluster_path, |cl| { - cl.meta.name = override_title - .clone() - .unwrap_or_else(|| backup_name.to_string()); - cl.stage = ClusterStage::PackDownloading; - cl.meta.icon.clone_from(&icon); - cl.meta.mc_version.clone_from(&game_version); - cl.meta.loader_version.clone_from(&loader_version); - cl.meta.loader = mod_loader; - - async { Ok(()) } - }) - .await?; - - let state = State::get().await?; - let ingress = copy_minecraft( - cluster_path.clone(), - gdlauncher_instance_folder, - &state.io_semaphore, - None, - ) - .await?; - - if let Some(cluster_val) = crate::api::cluster::get(&cluster_path).await? { - crate::game::install_minecraft(&cluster_val, Some(ingress), false).await?; - { - let state = State::get().await?; - let mut watcher = state.watcher.write().await; - Cluster::watch(&cluster_val.get_full_path().await?, &mut watcher).await?; - } - State::sync().await?; - } - - Ok(()) -} diff --git a/packages/core_old/src/api/package/import/mod.rs b/packages/core_old/src/api/package/import/mod.rs deleted file mode 100644 index 7e1c8d15..00000000 --- a/packages/core_old/src/api/package/import/mod.rs +++ /dev/null @@ -1,318 +0,0 @@ -//! Manages importing data from other launchers. - -use crate::prelude::ClusterPath; -use crate::proxy::send::{init_or_edit_ingress, send_ingress}; -use crate::proxy::IngressId; - -use crate::store::Clusters; -use crate::utils::http::{self, IoSemaphore}; -use onelauncher_utils::io::{self, IOError}; -use serde::{Deserialize, Serialize}; -use std::fmt; -use std::path::{Path, PathBuf}; - -pub mod atlauncher; -pub mod curseforge; -pub mod ftb; -pub mod gdlauncher; -pub mod modrinth; -pub mod multibased; -pub mod technic; -pub mod tlauncher; - -/// List of launcher types we support importing from. -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] -pub enum ImportType { - /// `MultiMC` based launchers - MultiMC, - /// Prism Launcher has its own category because it has different logic (also is objectively better than mmc) - PrismLauncher, - /// `GDLauncher` - GDLauncher, - /// Curseforge's launcher - Curseforge, - /// `ATLauncher` - ATLauncher, - /// Modrinth app. - Modrinth, - /// `TLauncher` - TLauncher, - /// FTB Launcher - FTBLauncher, - /// Technic - Technic, - /// Unknown import option (not widely adopted -> probably a custom launcher with a similar file structure to the above) - #[serde(other)] - Unknown, -} - -impl fmt::Display for ImportType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::MultiMC => write!(f, "MultiMC"), - Self::ATLauncher => write!(f, "ATLauncher"), - Self::Curseforge => write!(f, "Curseforge"), - Self::GDLauncher => write!(f, "GDLauncher"), - Self::Modrinth => write!(f, "Modrinth App"), - Self::PrismLauncher => write!(f, "PrismLauncher"), - Self::TLauncher => write!(f, "TLauncher"), - Self::FTBLauncher => write!(f, "Feed The Beast"), - Self::Technic => write!(f, "Technic"), - Self::Unknown => write!(f, "Unknown"), - } - } -} - -impl ImportType { - pub async fn get_instances_subpath(&self, path: &Path) -> crate::Result { - Ok(path.join( - &(match self { - Self::GDLauncher | Self::ATLauncher => "instances".to_string(), - Self::Curseforge => "Instances".to_string(), - Self::MultiMC => { - multibased::get_instances_subpath(path.to_path_buf().join("multimc.cfg")) - .await - .unwrap_or_else(|| "instances".to_string()) - } - Self::PrismLauncher => { - multibased::get_instances_subpath(path.to_path_buf().join("prismlauncher.cfg")) - .await - .unwrap_or_else(|| "instances".to_string()) - } - Self::Modrinth => "profiles".to_string(), - Self::TLauncher => "tinstances".to_string(), - Self::FTBLauncher => "ftbinstances".to_string(), - Self::Technic => "tecinstances".to_string(), - Self::Unknown => { - return Err(anyhow::anyhow!("launcher type unknown, cant import").into()) - } - }), - )) - } -} - -pub async fn get_launcher_instances(import: ImportType, path: Option) -> crate::Result<(PathBuf, Vec)> { - let base_path = match &path { - Some(path) => path, - None => &default_launcher_path(import).ok_or_else(|| anyhow::anyhow!("could not get launcher base path for {import}"))? - }; - - let instances_dir = import.get_instances_subpath(base_path).await?; - - let mut instances = Vec::new(); - let mut dir = io::read_dir(&instances_dir) - .await - .map_err(|_| anyhow::anyhow!("invalid {import} launcher path, failed to import."))?; - - while let Some(e) = dir - .next_entry() - .await - .map_err(|e| IOError::with_path(e, &instances_dir))? - { - let path = e.path(); - if path.is_dir() && is_valid_instance(path.clone(), import).await { - let name = path.file_name(); - if let Some(name) = name { - instances.push(name.to_string_lossy().to_string()); - } - } - } - - Ok((base_path.to_owned(), instances)) -} - -#[tracing::instrument] -pub async fn import_instances( - import: ImportType, - base_path: PathBuf, - instances: Vec, -) -> crate::Result<()> { - for instance in instances { - import_instance(import, base_path.clone(), instance).await?; - } - - Ok(()) -} - -#[tracing::instrument] -#[onelauncher_macros::memory] -pub async fn import_instance( - import: ImportType, - base_path: PathBuf, - instance_path: String, -) -> crate::Result<()> { - tracing::debug!("importing instance from {instance_path}"); - - let cluster_path = crate::cluster::create::create_unfinished_cluster(instance_path.clone()).await?; - - let result = match import { - ImportType::MultiMC | ImportType::PrismLauncher => { - multibased::import_mmc(base_path, instance_path, cluster_path.clone()).await - } - ImportType::ATLauncher => { - atlauncher::import_atlauncher(base_path, instance_path, cluster_path.clone()).await - } - ImportType::GDLauncher => { - gdlauncher::import_gdlauncher( - base_path.join("instances").join(instance_path), - cluster_path.clone(), - ) - .await - } - ImportType::Curseforge => { - curseforge::import_curseforge( - base_path.join("Instances").join(instance_path), - cluster_path.clone(), - ) - .await - } - ImportType::Modrinth => { - modrinth::import_modrinth( - base_path.join("profiles").join(instance_path), - cluster_path.clone(), - ) - .await - } - // ImportType::TLauncher => tlauncher::import_tlauncher(path.join("instances").join(instance_path), cluster_path.clone()).await, - // ImportType::Technic => technic::import_technic(path.join("instances").join(instance_path), cluster_path.clone()).await, - // ImportType::FTBLauncher => ftb::import_ftb(path.join("instances").join(instance_path), cluster_path.clone()).await, - ImportType::Unknown => Err(anyhow::anyhow!("unknown launcher type").into()), - _ => todo!(), - }; - - match result { - Ok(()) => {} - Err(e) => { - tracing::warn!("failed to import modpack: {:?}", e); - let _ = crate::api::cluster::remove(&cluster_path).await; - return Err(e); - } - } - - tokio::task::spawn(Clusters::update_versions()); - - tracing::debug!("completed import of instance."); - Ok(()) -} - -/// returns the default path for a given [`ImportType`]. -#[must_use] -pub fn default_launcher_path(r#type: ImportType) -> Option { - let path = match r#type { - ImportType::PrismLauncher => Some(dirs::data_dir()?.join("PrismLauncher")), - ImportType::ATLauncher => Some(dirs::data_dir()?.join("ATLauncher")), - ImportType::GDLauncher => Some(dirs::data_dir()?.join("gdlauncher_next")), - ImportType::Curseforge => Some(dirs::home_dir()?.join("curseforge").join("minecraft")), - ImportType::Modrinth => Some(dirs::data_dir()?.join("theseus")), - ImportType::FTBLauncher => Some(dirs::data_dir()?.join("FTB")), - ImportType::Technic => Some(dirs::data_dir()?.join("Technic")), - ImportType::TLauncher => Some(dirs::data_dir()?.join("TLauncher")), - ImportType::Unknown | ImportType::MultiMC => None, // MultiMC data is in it's application directory - }; - - let path = path?; - if path.exists() { - Some(path) - } else { - None - } -} - -/// Checks if a [`PathBuf`] is a valid instance for a given [`ImportType`] -#[tracing::instrument] -#[onelauncher_macros::memory] -pub async fn is_valid_instance(instance_path: PathBuf, r#type: ImportType) -> bool { - match r#type { - ImportType::MultiMC | ImportType::PrismLauncher => { - multibased::is_valid_multibased(instance_path).await - } - ImportType::ATLauncher => atlauncher::is_valid_atlauncher(instance_path).await, - ImportType::GDLauncher => gdlauncher::is_valid_gdlauncher(instance_path).await, - ImportType::Curseforge => curseforge::is_valid_curseforge(instance_path).await, - // ImportType::Modrinth => modrinth::is_valid_modrinth(instance_path).await, - // ImportType::TLauncher => tlauncher::is_valid_tlauncher(instance_path).await, - // ImportType::FTBLauncher => ftb::is_valid_ftb(instance_path).await, - // ImportType::Technic => technic::is_valid_technic(instance_path).await, - ImportType::Unknown => false, - _ => todo!(), - } -} - -#[tracing::instrument] -#[onelauncher_macros::memory] -pub async fn cache_icon(icon_path: PathBuf) -> crate::Result> { - let state = crate::State::get().await?; - let bytes = tokio::fs::read(&icon_path).await; - if let Ok(bytes) = bytes { - let bytes = bytes::Bytes::from(bytes); - let cache_dir = &state.directories.caches_dir().await; - let semaphore = &state.io_semaphore; - Ok(Some( - http::write_icon(&icon_path.to_string_lossy(), cache_dir, bytes, semaphore).await?, - )) - } else { - Ok(None) - } -} - -pub async fn copy_minecraft( - cluster_path: ClusterPath, - minecraft_path: PathBuf, - io_semaphore: &IoSemaphore, - old_ingress: Option, -) -> crate::Result { - let cluster_path_full = cluster_path.full_path().await?; - let subfiles = sub(&minecraft_path, false).await?; - let total_subfiles = subfiles.len() as u64; - let ingress = init_or_edit_ingress( - old_ingress, - crate::IngressType::CopyCluster { - import: minecraft_path.clone(), - cluster_name: cluster_path.to_string(), - }, - total_subfiles as f64, - "copying files in preexisting cluster", - ) - .await?; - - for sub in subfiles { - let child = sub - .strip_prefix(&minecraft_path) - .map_err(|_| anyhow::anyhow!("invalid .minecraft file {}", &sub.display()))?; - let child = cluster_path_full.join(child); - tokio::time::sleep(std::time::Duration::from_millis(1)).await; - http::copy(&sub, &child, io_semaphore).await?; - send_ingress(&ingress, 1.0, None).await?; - } - - Ok(ingress) -} - -/// recursively get a [`Vec`] of all subfiles. -#[onelauncher_macros::memory] -#[async_recursion::async_recursion] -#[tracing::instrument] -pub async fn sub(path: &Path, include_empty: bool) -> crate::Result> { - if !path.is_dir() { - return Ok(vec![path.to_path_buf()]); - } - let mut files = Vec::new(); - let mut dir = io::read_dir(&path).await?; - let mut has_files = false; - while let Some(child) = dir - .next_entry() - .await - .map_err(|e| IOError::with_path(e, path))? - { - has_files = true; - let path_child = child.path(); - files.append(&mut sub(&path_child, include_empty).await?); - } - - if !has_files && include_empty { - files.push(path.to_path_buf()); - } - - Ok(files) -} diff --git a/packages/core_old/src/api/package/import/modrinth.rs b/packages/core_old/src/api/package/import/modrinth.rs deleted file mode 100644 index 8bb632a7..00000000 --- a/packages/core_old/src/api/package/import/modrinth.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! Launcher Import: Modrinth App -//! Source Code available at - -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; - -use crate::prelude::ClusterPath; -use onelauncher_utils::io; - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct ModrinthProfile {} - -pub async fn is_valid_modrinth(instance_folder: PathBuf) -> bool { - let config: String = io::read_to_string(&instance_folder.join("profile.json")) - .await - .unwrap_or(String::new()); - let config: Result = - serde_json::from_str::(&config); - config.is_ok() -} - -pub async fn import_modrinth( - _modrinth_instance_folder: PathBuf, - _cluster_path: ClusterPath, -) -> crate::Result<()> { - Ok(()) -} diff --git a/packages/core_old/src/api/package/import/multibased.rs b/packages/core_old/src/api/package/import/multibased.rs deleted file mode 100644 index d4366b00..00000000 --- a/packages/core_old/src/api/package/import/multibased.rs +++ /dev/null @@ -1,307 +0,0 @@ -//! Launcher Import: `MultiMC` & Prism Launcher -//! Source Code available at -//! Source Code available at - -use serde::{de, Deserialize, Serialize}; -use std::path::{Path, PathBuf}; - -use crate::package::from::{self, CreatePackDescription, PackDependency}; -use crate::package::import::{self, copy_minecraft}; -use crate::prelude::{Cluster, ClusterPath, State}; -use onelauncher_utils::io; - -// instance.cfg: https://github.com/PrismLauncher/PrismLauncher/blob/develop/launcher/minecraft/MinecraftInstance.cpp -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "PascalCase")] -#[serde(untagged)] -enum MMCInstanceEnum { - General(MMCInstanceGeneral), - Instance(MMCInstance), -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "PascalCase")] -struct MMCInstanceGeneral { - pub general: MMCInstance, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "PascalCase")] -pub struct MMCInstance { - pub java_path: Option, - pub jvm_args: Option, - - #[serde(default)] - #[serde(deserialize_with = "deserialize_optional_bool")] - pub managed_pack: Option, - - #[serde(rename = "ManagedPackID")] - pub managed_pack_id: Option, - pub managed_pack_type: Option, - #[serde(rename = "ManagedPackVersionID")] - pub managed_pack_version_id: Option, - pub managed_pack_version_name: Option, - - #[serde(rename = "iconKey")] - pub icon_key: Option, - #[serde(rename = "name")] - pub name: Option, -} - -/// Deserializes INI String Boolean values into Rust Boolean values for [`serde-ini`]. -fn deserialize_optional_bool<'de, D>(deserializer: D) -> Result, D::Error> -where - D: de::Deserializer<'de>, -{ - Option::::deserialize(deserializer)?.map_or_else( - || Ok(None), - |string| match string.as_str() { - "true" => Ok(Some(true)), - "false" => Ok(Some(false)), - _ => Err(de::Error::custom("expected 'true' or 'false'")), - }, - ) -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "lowercase")] -pub enum MMCManagedPackType { - Modrinth, - Flame, - ATLauncher, - #[serde(other)] - Unknown, -} - -// mmc-pack.json -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct MMCPack { - components: Vec, - format_version: u32, -} - -// https://github.com/PrismLauncher/PrismLauncher/blob/develop/launcher/minecraft/Component.h -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct MMCComponent { - pub uid: String, - - #[serde(default)] - pub version: Option, - #[serde(default)] - pub dependency_only: bool, - - #[serde(default)] - pub important: bool, - #[serde(default)] - pub disabled: bool, - - pub cached_name: Option, - pub cached_version: Option, - - #[serde(default)] - pub cached_requires: Vec, - #[serde(default)] - pub cached_conflicts: Vec, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct MMCComponentRequirement { - pub uid: String, - pub equals_version: Option, - pub suggests: Option, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "PascalCase")] -#[serde(untagged)] -enum MMCLauncherEnum { - General(MMCLauncherGeneral), - Instance(MMCLauncher), -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "PascalCase")] -struct MMCLauncherGeneral { - pub general: MMCLauncher, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "PascalCase")] -pub struct MMCLauncher { - instance_dir: String, -} - -#[tracing::instrument] -pub async fn is_valid_multibased(instance_folder: PathBuf) -> bool { - let instance_cfg = instance_folder.join("instance.cfg"); - let mmc_pack = instance_folder.join("mmc-pack.json"); - let Ok(mmc_pack) = io::read_to_string(&mmc_pack).await else { - return false; - }; - - load_instance_cfg(&instance_cfg).await.is_ok() - && serde_json::from_str::(&mmc_pack).is_ok() -} - -#[tracing::instrument] -pub async fn get_instances_subpath(config: PathBuf) -> Option { - let launcher = io::read_to_string(&config).await.ok()?; - let launcher: MMCLauncherEnum = serde_ini::from_str(&launcher).ok()?; - match launcher { - MMCLauncherEnum::General(p) => Some(p.general.instance_dir), - MMCLauncherEnum::Instance(p) => Some(p.instance_dir), - } -} - -async fn load_instance_cfg(file_path: &Path) -> crate::Result { - let instance_cfg: String = io::read_to_string(file_path).await?; - let instance_cfg_enum: MMCInstanceEnum = serde_ini::from_str::(&instance_cfg)?; - match instance_cfg_enum { - MMCInstanceEnum::General(instance_cfg) => Ok(instance_cfg.general), - MMCInstanceEnum::Instance(instance_cfg) => Ok(instance_cfg), - } -} - -#[tracing::instrument] -#[onelauncher_macros::memory] -pub async fn import_mmc( - mmc_base_path: PathBuf, - instance_folder: String, - cluster_path: ClusterPath, -) -> crate::Result<()> { - let mmc_instance_path = mmc_base_path - .join("instances") - .join(instance_folder.clone()); - let mmc_pack = io::read_to_string(&mmc_instance_path.join("mmc-pack.json")).await?; - let mmc_pack: MMCPack = serde_json::from_str::(&mmc_pack)?; - let instance_cfg = load_instance_cfg(&mmc_instance_path.join("instance.cfg")).await?; - - let icon = if let Some(icon_key) = instance_cfg.icon_key { - let icon_path = mmc_base_path.join("icons").join(icon_key); - import::cache_icon(icon_path).await? - } else { - None - }; - - let description = CreatePackDescription { - icon, - override_title: instance_cfg.name, - package_id: instance_cfg.managed_pack_id, - version_id: instance_cfg.managed_pack_version_id, - existing_ingress: None, - cluster_path: cluster_path.clone(), - }; - - let mut minecraft_folder = mmc_base_path - .join("instances") - .join(instance_folder); - - if minecraft_folder.join("minecraft").exists() { - minecraft_folder = minecraft_folder.join("minecraft"); - } else if minecraft_folder.join(".minecraft").exists() { - minecraft_folder = minecraft_folder.join(".minecraft"); - } - - let backup_name = if instance_cfg.managed_pack.unwrap_or(false) { - match instance_cfg.managed_pack_type { - Some(MMCManagedPackType::Modrinth) => "Imported Modrinth Modpack".to_string(), - Some(MMCManagedPackType::Flame) => "Imported CurseForge Modpack".to_string(), - Some(MMCManagedPackType::ATLauncher) => "Imported ATLauncher Modpack".to_string(), - Some(_) => "ImportedModpack".to_string(), - - _ => { - return Err(anyhow::anyhow!( - "Instance is managed, but managed pack type not specified in instance.cfg" - ) - .into()) - } - } - } else { - "Imported Modpack".to_string() - }; - - import_mmc_unmanaged( - cluster_path, - minecraft_folder, - backup_name, - description, - mmc_pack, - ) - .await?; - - Ok(()) -} - -async fn import_mmc_unmanaged( - cluster_path: ClusterPath, - minecraft_folder: PathBuf, - backup_name: String, - description: CreatePackDescription, - mmc_pack: MMCPack, -) -> crate::Result<()> { - let dependencies = mmc_pack - .components - .iter() - .filter_map(|component| { - if component.uid.starts_with("net.fabricmc.fabric-loader") { - return Some(( - PackDependency::FabricLoader, - component.version.clone().unwrap_or_default(), - )); - } - if component.uid.starts_with("net.minecraftforge") { - return Some(( - PackDependency::Forge, - component.version.clone().unwrap_or_default(), - )); - } - if component.uid.starts_with("org.quiltmc.quilt-loader") { - return Some(( - PackDependency::QuiltLoader, - component.version.clone().unwrap_or_default(), - )); - } - if component.uid.starts_with("net.minecraft") { - return Some(( - PackDependency::Minecraft, - component.version.clone().unwrap_or_default(), - )); - } - - None - }) - .collect(); - - from::set_cluster_information::( - cluster_path.clone(), - &description, - &backup_name, - &dependencies, - false, - ) - .await?; - - let state = State::get().await?; - let ingress = copy_minecraft( - cluster_path.clone(), - minecraft_folder, - &state.io_semaphore, - None, - ) - .await?; - - if let Some(cluster_val) = crate::api::cluster::get(&cluster_path).await? { - crate::game::install_minecraft(&cluster_val, Some(ingress), false).await?; - { - let state = State::get().await?; - let mut watcher = state.watcher.write().await; - Cluster::watch(&cluster_val.get_full_path().await?, &mut watcher).await?; - } - State::sync().await?; - } - - Ok(()) -} diff --git a/packages/core_old/src/api/package/import/technic.rs b/packages/core_old/src/api/package/import/technic.rs deleted file mode 100644 index 0692d725..00000000 --- a/packages/core_old/src/api/package/import/technic.rs +++ /dev/null @@ -1,2 +0,0 @@ -//! Launcher Import: Technic Launcher -//! Source Code available at diff --git a/packages/core_old/src/api/package/import/tlauncher.rs b/packages/core_old/src/api/package/import/tlauncher.rs deleted file mode 100644 index defc6b3e..00000000 --- a/packages/core_old/src/api/package/import/tlauncher.rs +++ /dev/null @@ -1,2 +0,0 @@ -//! Launcher Import: `TLauncher` -//! Closed source (technically) ;3 diff --git a/packages/core_old/src/api/package/infer/mod.rs b/packages/core_old/src/api/package/infer/mod.rs deleted file mode 100644 index 9367d137..00000000 --- a/packages/core_old/src/api/package/infer/mod.rs +++ /dev/null @@ -1,254 +0,0 @@ -// //! **OneLauncher Inferral** -// //! -// //! Infers package metadata beyond just looking up the file hashes. - -// use std::collections::HashMap; -// use std::future::Future; -// use std::path::PathBuf; -// use std::pin::Pin; - -// use async_zip::tokio::read::fs::ZipFileReader; -// use serde::Deserialize; - -// use crate::prelude::PackageType; -// use crate::store::{read_icon, Package, PackageMetadata}; -// use crate::utils::http::{FetchSemaphore, IoSemaphore}; - -// /// A structure representing a package file that hasn't been analyzed. -// pub struct UnanalyzedFile { -// path: String, -// file_name: String, -// package_type: PackageType, -// size: u64, -// cache: String, -// } - -// #[derive(Deserialize)] -// #[serde(rename_all = "camelCase")] -// /// A structure representing a `jar/META-INF/mods.toml` Forge metadata file. -// pub struct ForgeModsInfo { -// pub mods: Vec, -// } - -// #[derive(Deserialize)] -// #[serde(rename_all = "camelCase")] -// /// A structure representing `jar/META-INF/mods.toml` Forge mod metadata. -// pub struct ForgeMods { -// mod_id: String, -// version: Option, -// display_name: Option, -// description: Option, -// logo_file: Option, -// authors: Option, -// } - -// #[derive(Deserialize)] -// #[serde(rename_all = "camelCase")] -// /// A structure representing a `jar/mcmod.info` Forge metadata file. -// pub struct ForgeMcMod { -// modid: String, -// name: String, -// description: Option, -// version: Option, -// author_list: Option>, -// logo_file: Option, -// } - -// #[derive(Deserialize)] -// #[serde(untagged)] -// /// A structure representing `jar/fabric.mod.json` Fabric author metadata. -// pub enum FabricAuthor { -// String(String), -// Object { name: String }, -// } - -// #[derive(Deserialize)] -// #[serde(rename_all = "camelCase")] -// /// A structure representing a `jar/fabric.mod.json` Fabric metadata file. -// pub struct FabricMod { -// id: String, -// version: String, -// name: Option, -// description: Option, -// authors: Vec, -// icon: Option, -// } - -// #[derive(Deserialize)] -// /// A structure representing `jar/quilt.mod.json` Quilt metadata. -// pub struct QuiltMetadata { -// name: Option, -// description: Option, -// contributors: Option>, -// icon: Option, -// } - -// #[derive(Deserialize)] -// /// A structure representing a `jar/quilt.mod.json` Quilt metadata file. -// pub struct QuiltMod { -// id: String, -// version: String, -// metadata: Option, -// } - -// #[derive(Deserialize)] -// /// A structure representing a `zip/pack.mcmeta` package metadata file. -// pub struct Pack { -// description: Option, -// } - -// async fn process( -// zipfr: &ZipFileReader, -// disabled: bool, -// file_name: String, -// sha512: String, -// index: usize, -// reader_fn: F, -// meta_fn: M, -// ) -> crate::Result -// where -// F: Fn(&ZipFileReader, usize) -> Pin>>>, -// M: Fn(String) -> Option, -// { -// let filestr = reader_fn(zipfr, index).await?; - -// if let Some(meta) = meta_fn(filestr) { -// Ok(Package { -// sha512, -// disabled, -// file_name, -// meta, -// }) -// } else { -// Ok(Package { -// sha512, -// disabled, -// file_name, -// meta: PackageMetadata::Unknown, -// }) -// } -// } - -// fn forge_metadata(filestr: String) -> Option { -// if let Ok(pkg) = toml::from_str::(&filestr) { -// if let Some(pkg) = pkg.mods.first() { -// return Some(PackageMetadata::Mapped { -// title: Some( -// pkg.display_name -// .clone() -// .unwrap_or_else(|| pkg.mod_id.clone()), -// ), -// description: pkg.description.clone(), -// authors: pkg.authors.clone().map(|x| vec![x]).unwrap_or_default(), -// version: pkg.version.clone(), -// icon: pkg.logo_file.clone(), -// package_type: Some(PackageType::Mod), -// }); -// } -// } - -// None -// } - -// pub async fn infer( -// hash: String, -// path: PathBuf, -// cache_dir: PathBuf, -// io_semaphore: &IoSemaphore, -// fetch_semaphore: &FetchSemaphore, -// ) -> crate::Result { -// let file_name = path -// .file_name() -// .unwrap_or_default() -// .to_string_lossy() -// .to_string(); -// let disabled = file_name.ends_with(".disabled"); -// let zipfr = match ZipFileReader::new(path.clone()).await { -// Ok(zipfr) => zipfr, -// Err(_) => { -// return Ok(Package { -// sha512: hash, -// disabled, -// meta: PackageMetadata::Unknown, -// file_name, -// }) -// } -// }; - -// let zip_idx = zipfr -// .file() -// .entries() -// .iter() -// .position(|f| f.filename().as_str().unwrap_or_default() == "META-INF/mods.toml"); -// if let Some(idx) = zip_idx { -// let mut filestr = String::new(); -// if zipfr -// .reader_with_entry(idx) -// .await? -// .read_to_string_checked(&mut filestr) -// .await -// .is_ok() -// { -// if let Ok(pkg) = toml::from_str::(&filestr) { -// if let Some(pkg) = pkg.mods.first() { -// return Ok(Package { -// sha512: hash, -// disabled, -// file_name, -// meta: PackageMetadata::Mapped { -// title: Some( -// pkg.display_name -// .clone() -// .unwrap_or_else(|| pkg.mod_id.clone()), -// ), -// description: pkg.description.clone(), -// authors: pkg.authors.clone().map(|x| vec![x]).unwrap_or_default(), -// version: pkg.version.clone(), -// icon: read_icon(pkg.logo_file.clone(), &cache_dir, &path, io_semaphore) -// .await?, -// package_type: Some(PackageType::Mod), -// }, -// }); -// } -// } -// } -// } - -// let zip_idx = zipfr -// .file() -// .entries() -// .iter() -// .position(|f| f.filename().as_str().unwrap_or_default() == "mcmod.info"); -// if let Some(idx) = zip_idx { -// let mut filestr = String::new(); -// if zipfr -// .reader_with_entry(idx) -// .await? -// .read_to_string_checked(&mut filestr) -// .await -// .is_ok() -// { -// if let Ok(pkg) = serde_json::from_str::(&filestr) { -// let icon = read_icon(pkg.logo_file, &cache_dir, &path, io_semaphore).await?; - -// return Ok(Package { -// sha512: hash, -// disabled, -// file_name, -// meta: PackageMetadata::Mapped { -// title: Some(if pkg.name.is_empty() { -// pkg.modid -// } else { -// pkg.name -// }), -// description: pkg.description, -// authors: pkg.author_list.unwrap_or_default(), -// version: pkg.version, -// icon, -// package_type: Some(PackageType::Mod), -// }, -// }); -// } -// } -// } -// } diff --git a/packages/core_old/src/api/package/mod.rs b/packages/core_old/src/api/package/mod.rs deleted file mode 100644 index e4d57166..00000000 --- a/packages/core_old/src/api/package/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! **`OneLauncher` Package** -//! -//! Utilities for importing packages from and to `OneLauncher`. - -pub mod content; -pub mod from; -pub mod import; -pub mod infer; -pub mod mrpack; -pub mod zip; diff --git a/packages/core_old/src/api/package/mrpack.rs b/packages/core_old/src/api/package/mrpack.rs deleted file mode 100644 index e66d217f..00000000 --- a/packages/core_old/src/api/package/mrpack.rs +++ /dev/null @@ -1,338 +0,0 @@ -//! Import and Export `.mrpack` mod packs. - -use crate::package::from::{set_cluster_information, EnvType, PackFile, PackFileHash}; -use crate::prelude::ClusterPath; -use crate::proxy::ingress_try_for_each; -use crate::proxy::send::{init_or_edit_ingress, send_ingress}; -use crate::store::{ClusterStage, Clusters, PackageSide}; -use crate::utils::http::{fetch_from_mirrors, write}; -use crate::{cluster, IngressType, State}; -use async_zip::base::read::seek::ZipFileReader; -use onelauncher_utils::io; - -use futures::StreamExt; -use std::io::Cursor; -use std::path::{Component, PathBuf}; - -use super::from::{ - generate_pack_from_file, generate_pack_from_version_id, CreatePack, CreatePackLocation, - PackFormat, -}; - -/// Install a pack -/// Wrapper around [`install_pack_files`] that generates a pack creation description, and -/// attempts to install the pack files. If it fails, it will remove the cluster (fail safely) -/// Install a modpack from a mrpack file (a modrinth .zip format) -#[onelauncher_macros::memory] -pub async fn install_zipped_mrpack( - location: CreatePackLocation, - cluster_path: ClusterPath, -) -> crate::Result { - let create_pack: CreatePack = match location { - CreatePackLocation::FromModrinth { - package_id, - version_id, - title, - icon_url, - } => { - generate_pack_from_version_id( - package_id, - version_id, - title, - icon_url, - cluster_path.clone(), - None, - ) - .await? - } - CreatePackLocation::FromFile { path } => { - generate_pack_from_file(path, cluster_path.clone()).await? - } - }; - - let result = install_zipped_mrpack_files(create_pack, false).await; - tokio::task::spawn(Clusters::update_versions()); - - match result { - Ok(cluster) => Ok(cluster), - Err(err) => { - let _ = cluster::remove(&cluster_path).await; - - Err(err) - } - } -} - -/// Install all pack files from a description. Does not remove the cluster if it fails -#[onelauncher_macros::memory] -pub async fn install_zipped_mrpack_files( - create_pack: CreatePack, - ignore_lock: bool, -) -> crate::Result { - let state = &State::get().await?; - let file = create_pack.file; - let description = create_pack.description.clone(); // make a copy for cluster::edit - let icon = create_pack.description.icon; - let package_id = create_pack.description.package_id; - let version_id = create_pack.description.version_id; - let existing_ingress = create_pack.description.existing_ingress; - let cluster_path = create_pack.description.cluster_path; - let icon_exists = icon.is_some(); - let reader: Cursor<&bytes::Bytes> = Cursor::new(&file); - - let mut zip_reader = ZipFileReader::with_tokio(reader).await?; - - let zip_index_option = zip_reader - .file() - .entries() - .iter() - .position(|f| f.filename().as_str().unwrap_or_default() == "modrinth.index.json"); - if let Some(zip_index) = zip_index_option { - let mut manifest = String::new(); - let mut reader = zip_reader.reader_with_entry(zip_index).await?; - reader.read_to_string_checked(&mut manifest).await?; - let pack: PackFormat = serde_json::from_str(&manifest)?; - - if &*pack.game != "minecraft" { - return Err(anyhow::anyhow!("pack doesn't support Minecraft").into()); - } - - set_cluster_information( - cluster_path.clone(), - &description, - &pack.name, - &pack.dependencies, - ignore_lock, - ) - .await?; - - let cluster_path = cluster_path.clone(); - let ingress = init_or_edit_ingress( - existing_ingress, - IngressType::DownloadPackage { - cluster_path: cluster_path.full_path().await?.clone(), - package_name: pack.name.clone(), - icon, - package_id, - package_version: version_id, - }, - 100.0, - "downloading modpack", - ) - .await?; - - let num_files = pack.files.len(); - ingress_try_for_each( - futures::stream::iter(pack.files.into_iter()).map(Ok::), - None, - Some(&ingress), - 70.0, - num_files, - None, - |pack| { - let cluster_path = cluster_path.clone(); - async move { - // TODO: prompt user for optional files in a modpack - if let Some(env) = pack.env { - if env - .get(&EnvType::Client) - .is_some_and(|x| x == &PackageSide::Unsupported) - { - return Ok(()); - } - } - - let file = fetch_from_mirrors( - &pack.downloads.iter().map(|x| &**x).collect::>(), - pack.hashes.get(&PackFileHash::Sha1).map(|x| &**x), - &state.fetch_semaphore, - ) - .await?; - - let package_path = pack.path.to_string(); - let path = std::path::Path::new(&package_path).components().next(); - if let Some(Component::CurDir | Component::Normal(_)) = path { - let path = cluster_path.full_path().await?.join(&package_path); - write(&path, &file, &state.io_semaphore).await?; - }; - Ok(()) - } - }, - ) - .await?; - - send_ingress(&ingress, 0.0, Some("extracting overrides")).await?; - - let mut total_len = 0; - - for index in 0..zip_reader.file().entries().len() { - let file = zip_reader.file().entries().get(index).unwrap(); - let filename = file.filename().as_str().unwrap_or_default(); - - if (filename.starts_with("overrides") || filename.starts_with("client-overrides")) - && !filename.ends_with('/') - { - total_len += 1; - } - } - - for index in 0..zip_reader.file().entries().len() { - let file = zip_reader.file().entries().get(index).unwrap(); - - let filename = file.filename().as_str().unwrap_or_default(); - - let file_path = PathBuf::from(filename); - if (filename.starts_with("overrides") || filename.starts_with("client-overrides")) - && !filename.ends_with('/') - { - let mut content = Vec::new(); - let mut reader = zip_reader.reader_with_entry(index).await?; - reader.read_to_end_checked(&mut content).await?; - - let mut new_path = PathBuf::new(); - let components = file_path.components().skip(1); - - for component in components { - new_path.push(component); - } - - if new_path.file_name().is_some() { - write( - &cluster_path.full_path().await?.join(new_path), - &content, - &state.io_semaphore, - ) - .await?; - } - - send_ingress( - &ingress, - 30.0 / f64::from(total_len), - Some(&format!("extracting override {index}/{total_len}")), - ) - .await?; - } - } - - let potential_icon = cluster_path.full_path().await?.join("icon.png"); - if !icon_exists && potential_icon.exists() { - cluster::edit_icon(&cluster_path, Some(&potential_icon)).await?; - } - - if let Some(cluster) = cluster::get(&cluster_path).await? { - crate::game::install_minecraft(&cluster, Some(ingress), false).await?; - - State::sync().await?; - } - - Ok::(cluster_path.clone()) - } else { - Err(anyhow::anyhow!("no pack manifest found in mrpack").into()) - } -} - -#[tracing::instrument(skip(mrpack_file))] -#[onelauncher_macros::memory] -pub async fn remove_all_related_files( - cluster_path: ClusterPath, - mrpack_file: bytes::Bytes, -) -> crate::Result<()> { - let reader: Cursor<&bytes::Bytes> = Cursor::new(&mrpack_file); - - let mut zip_reader = ZipFileReader::with_tokio(reader).await?; - - let zip_index_option = zip_reader - .file() - .entries() - .iter() - .position(|f| f.filename().as_str().unwrap_or_default() == "modrinth.index.json"); - if let Some(zip_index) = zip_index_option { - let mut manifest = String::new(); - - let mut reader = zip_reader.reader_with_entry(zip_index).await?; - reader.read_to_string_checked(&mut manifest).await?; - - let pack: PackFormat = serde_json::from_str(&manifest)?; - - if &*pack.game != "minecraft" { - return Err(anyhow::anyhow!("pack doesn't support Minecraft").into()); - } - - cluster::edit(&cluster_path, |cl| { - cl.stage = ClusterStage::PackDownloading; - async { Ok(()) } - }) - .await?; - - // let state = State::get().await?; - // TODO: Packages - // let all_hashes = pack - // .files - // .iter() - // .filter_map(|f| Some(f.hashes.get(&PackFileHash::Sha512)?.clone())) - // .collect::>(); - - // let files_url = format!("{}version_files", crate::constants::MODRINTH_API_URL); - - // let hash_packages = fetch_json::>( - // Method::POST, - // &files_url, - // None, - // Some(json!({ - // "hashes": all_hashes, - // "algorithm": "sha512", - // })), - // &state.fetch_semaphore, - // ) - // .await?; - // let to_remove = hash_packages - // .into_values() - // .map(|p| p.package_id) - // .collect::>(); - // let cluster = cluster::get(&cluster_path).await?.ok_or_else(|| { - // anyhow::anyhow!("{} is an unmanaged cluster!", cluster_path.to_string()) - // })?; - - // for (package_path, package) in &cluster.packages { - // if let PackageMetadata::Managed { package_id, .. } = &package.meta { - // if to_remove.contains(package_id) { - // let path = cluster.get_full_path().await?.join(package_path.0.clone()); - // if path.exists() { - // io::remove_file(&path).await?; - // } - // } - // } - // } - - for file in pack.files { - let path: PathBuf = cluster_path.full_path().await?.join(file.path.to_string()); - if path.exists() { - io::remove_file(&path).await?; - } - } - - for index in 0..zip_reader.file().entries().len() { - let file = zip_reader.file().entries().get(index).unwrap(); - let filename = file.filename().as_str().unwrap_or_default(); - let file_path = PathBuf::from(filename); - if (filename.starts_with("overrides") || filename.starts_with("client-overrides")) - && !filename.ends_with('/') - { - let mut new_path = PathBuf::new(); - let components = file_path.components().skip(1); - - for component in components { - new_path.push(component); - } - - let existing_file = cluster_path.full_path().await?.join(&new_path); - if existing_file.exists() { - io::remove_file(&existing_file).await?; - } - } - } - Ok(()) - } else { - Err(anyhow::anyhow!("no pack manifest found in mrpack").into()) - } -} diff --git a/packages/core_old/src/api/package/zip.rs b/packages/core_old/src/api/package/zip.rs deleted file mode 100644 index 4f8a87a9..00000000 --- a/packages/core_old/src/api/package/zip.rs +++ /dev/null @@ -1 +0,0 @@ -//! Import and Export `.zip` mod packs diff --git a/packages/core_old/src/api/processor.rs b/packages/core_old/src/api/processor.rs deleted file mode 100644 index 535e292b..00000000 --- a/packages/core_old/src/api/processor.rs +++ /dev/null @@ -1,232 +0,0 @@ -//! Process management interface - -pub use crate::store::{Cluster, InitHooks, JavaOptions, Memory, Resolution, Settings, State}; -use crate::store::{ClusterPath, ProcessorChild}; -use chrono::{DateTime, Utc}; -use uuid::Uuid; - -#[derive(serde::Serialize)] -#[cfg_attr(feature = "specta", derive(specta::Type))] -pub struct DetailedProcess { - pub uuid: Uuid, - pub user: Option, - pub started_at: DateTime, - pub pid: u32, -} - -impl DetailedProcess { - pub async fn from_processor_child(process: &ProcessorChild) -> Self { - let pid = process.current_child.read().await.id().unwrap_or(0); - Self { - uuid: process.uuid, - user: process.user, - started_at: process.started_at, - pid, - } - } -} - -/// get detailed processes by a [`ClusterPath`]. -#[tracing::instrument] -pub async fn get_process_detailed_by_id(uuid: Uuid) -> crate::Result { - let state = State::get().await?; - let processor = state.processor.read().await; - - let child = processor - .get(uuid) - .ok_or_else(|| anyhow::anyhow!("process not found"))?; - let child = child.read().await; - let pid = child - .current_child - .read() - .await - .id() - .ok_or_else(|| anyhow::anyhow!("process not found"))?; - Ok(DetailedProcess { - uuid, - user: child.user, - started_at: child.started_at, - pid, - }) -} - -/// get detailed processes by a [`ClusterPath`]. -#[tracing::instrument] -pub async fn get_processes_detailed_by_path( - path: ClusterPath, -) -> crate::Result> { - let state = State::get().await?; - let processor = state.processor.read().await; - - let uuids = processor.running_cluster(path).await?; - let mut processes = Vec::new(); - for uuid in uuids { - let child = processor - .get(uuid) - .ok_or_else(|| anyhow::anyhow!("process not found"))?; - let child = child.read().await; - let pid = child - .current_child - .read() - .await - .id() - .ok_or_else(|| anyhow::anyhow!("process not found"))?; - processes.push(DetailedProcess { - uuid, - user: child.user, - started_at: child.started_at, - pid, - }); - } - Ok(processes) -} - -/// check whether or not a process has completed by its [`Uuid`]. -#[tracing::instrument] -pub async fn uuid_is_finished(uuid: Uuid) -> crate::Result { - Ok(uuid_exit_status(uuid).await?.is_some()) -} - -/// get the user from a processor by its [`Uuid`]. -#[tracing::instrument] -pub async fn get_user_by_process(uuid: Uuid) -> crate::Result> { - let state = State::get().await?; - let processor = state.processor.read().await; - if let Some(processor) = processor.get(uuid) { - Ok(processor.read().await.user) - } else { - Err(anyhow::anyhow!("process not found").into()) - } -} - -/// get the user from a processor by its [`Uuid`]. -#[tracing::instrument] -pub async fn get_process_started_at(uuid: Uuid) -> crate::Result> { - let state = State::get().await?; - let processor = state.processor.read().await; - if let Some(processor) = processor.get(uuid) { - Ok(processor.read().await.started_at) - } else { - Err(anyhow::anyhow!("process not found").into()) - } -} - -/// check the exit status of a process by its [`Uuid`]. -#[tracing::instrument] -pub async fn uuid_exit_status(uuid: Uuid) -> crate::Result> { - let state = State::get().await?; - let processor = state.processor.read().await; - processor.exit_status(uuid).await -} - -/// get all process [`Uuid`]s. -#[tracing::instrument] -pub async fn get_uuids() -> crate::Result> { - let state = State::get().await?; - let processor = state.processor.read().await; - Ok(processor.keys()) -} - -/// get all running process [`Uuid`]s. -#[tracing::instrument] -pub async fn get_running() -> crate::Result> { - let state = State::get().await?; - let processor = state.processor.read().await; - processor.running().await -} - -/// get all running process' [`ClusterPath`]s. -#[tracing::instrument] -pub async fn get_running_cluster_paths() -> crate::Result> { - let state = State::get().await?; - let processor = state.processor.read().await; - processor.running_cluster_paths().await -} - -/// get all running process' [`Cluster`]s. -#[tracing::instrument] -pub async fn get_running_clusters() -> crate::Result> { - let state = State::get().await?; - let processor = state.processor.read().await; - processor.running_clusters().await -} - -/// check if a cluster is running by its [`Uuid`]. -#[tracing::instrument] -pub async fn is_cluster_running(uuid: Uuid) -> crate::Result { - let clusters = get_running_clusters().await?; - Ok(clusters.iter().any(|cluster| cluster.uuid == uuid)) -} - -/// get all processes by a [`ClusterPath`]. -#[tracing::instrument] -pub async fn get_uuids_by_cluster_path(cluster_path: ClusterPath) -> crate::Result> { - let state = State::get().await?; - let processor = state.processor.read().await; - processor.running_cluster(cluster_path).await -} - -/// kill an existing and running process by its [`Uuid`] -#[tracing::instrument] -pub async fn kill_by_uuid(uuid: Uuid) -> crate::Result<()> { - let state = State::get().await?; - let processor = state.processor.read().await; - - if let Some(process) = processor.get(uuid) { - let mut process = process.write().await; - kill(&mut process).await - } else { - Ok(()) - } -} - -/// wait for a child process to finish running its manager by its [`Uuid`]. -#[tracing::instrument] -pub async fn wait_for_by_uuid(uuid: Uuid) -> crate::Result<()> { - let state = State::get().await?; - let processor = state.processor.read().await; - - if let Some(process) = processor.get(uuid) { - let mut process = process.write().await; - wait_for(&mut process).await - } else { - Ok(()) - } -} - -/// kill an existing and running process -#[tracing::instrument(skip(process))] -pub async fn kill(process: &mut ProcessorChild) -> crate::Result<()> { - process.current_child.write().await.kill().await?; - Ok(()) -} - -/// wait for a child process to finish running its manager -#[tracing::instrument(skip(process))] -pub async fn wait_for(process: &mut ProcessorChild) -> crate::Result<()> { - process - .manager - .take() - .ok_or_else(|| anyhow::anyhow!("manager already completed for process {}", process.uuid))? - .await? - .map_err(|err| anyhow::anyhow!("failed to run minecraft: {err}"))?; - - Ok(()) -} - -/// get process pid by its [`Uuid`]. -#[tracing::instrument] -pub async fn get_pid_by_uuid(uuid: Uuid) -> crate::Result { - let state = State::get().await?; - let processor = state.processor.read().await; - Ok(processor - .get(uuid) - .ok_or_else(|| anyhow::anyhow!("process not found"))? - .read() - .await - .current_child - .read() - .await - .id() - .ok_or_else(|| anyhow::anyhow!("process not found"))?) -} diff --git a/packages/core_old/src/api/proxy/mod.rs b/packages/core_old/src/api/proxy/mod.rs deleted file mode 100644 index d8fb9bb4..00000000 --- a/packages/core_old/src/api/proxy/mod.rs +++ /dev/null @@ -1,277 +0,0 @@ -//! Proxy for Tauri/CLI specific features and communication -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::path::PathBuf; -use std::sync::Arc; -use tokio::sync::{OnceCell, RwLock}; -use uuid::Uuid; - -use crate::store::{ClusterPath, IngressProcessor}; - -pub mod send; -pub mod utils; -pub use utils::*; - -static PROXY_STATE: OnceCell> = OnceCell::const_new(); -pub struct ProxyState { - #[cfg(feature = "tauri")] - pub app: tauri::AppHandle, - pub ingress_feeds: RwLock>, -} - -#[derive(Serialize, Debug, Clone)] -pub struct Ingress { - pub ingress_uuid: Uuid, - pub message: String, - pub ingress_type: IngressType, - pub total: f64, - pub current: f64, - #[serde(skip)] - pub last_sent: f64, - #[cfg(feature = "cli")] - #[serde(skip)] - pub cli: indicatif::ProgressBar, -} - -#[derive(Serialize, Debug, Clone)] -pub struct IngressId(Uuid); - -impl Drop for IngressId { - #[clippy::has_significant_drop] - fn drop(&mut self) { - let ingress_uuid = self.0; - tokio::spawn(async move { - if let Ok(proxy_state) = ProxyState::get().await { - let mut ingress_feeds = proxy_state.ingress_feeds.write().await; - #[cfg(any(feature = "tauri", feature = "cli"))] - if let Some(ingress) = ingress_feeds.remove(&ingress_uuid) { - #[cfg(feature = "cli")] - { - let cli = ingress.cli; - cli.finish(); - } - - #[cfg(feature = "tauri")] - { - use tauri::Emitter; - let event = ingress.ingress_type.clone(); - let fraction = ingress.current / ingress.total; - - let _ = proxy_state.app.emit( - "ingress", - IngressPayload { - fraction: None, - message: "Complete".to_string(), - event, - ingress_uuid, - }, - ); - - tracing::trace!("exited at {fraction} for ingress: {:?}", ingress_uuid); - } - } - - #[cfg(not(any(feature = "tauri", feature = "cli")))] - ingress_feeds.remove(&ingress_uuid); - } - - if crate::State::initalized() { - let _ = IngressProcessor::finish( - crate::store::IngressProcessType::IngressFeed, - ingress_uuid, - ) - .await; - } - }); - } -} - -impl ProxyState { - #[cfg(feature = "tauri")] - pub async fn initialize(handle: &tauri::AppHandle) -> crate::Result> { - PROXY_STATE - .get_or_try_init(|| async { - Ok(Arc::new(Self { - app: handle.clone(), - ingress_feeds: RwLock::new(HashMap::new()), - })) - }) - .await - .cloned() - } - - #[cfg(not(feature = "tauri"))] - pub async fn initialize() -> crate::Result> { - PROXY_STATE - .get_or_try_init(|| async { - Ok(Arc::new(Self { - ingress_feeds: RwLock::new(HashMap::new()), - })) - }) - .await - .cloned() - } - - #[cfg(feature = "tauri")] - pub async fn get_main_window() -> crate::Result { - use tauri::Manager; - let value = Self::get().await?; - Ok(value.app.get_webview_window("main").unwrap()) - } - - #[allow(clippy::unused_async)] - #[cfg(feature = "tauri")] - pub async fn get() -> crate::Result> { - Ok(PROXY_STATE.get().ok_or(ProxyError::NotInitialized)?.clone()) - } - - #[cfg(not(feature = "tauri"))] - pub async fn get() -> crate::Result> { - Self::initialize().await - } - - pub async fn list_ingress_feeds() -> crate::Result> { - let value = Self::get().await?; - let read = value.ingress_feeds.read().await; - - let mut display_list: HashMap = HashMap::new(); - for (uuid, ingress) in read.iter() { - display_list.insert(*uuid, ingress.clone()); - } - - Ok(display_list) - } -} - -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)] -#[serde(tag = "type")] -#[serde(rename_all = "snake_case")] -pub enum IngressType { - Initialize, - DownloadJava { - version: u32, - }, - DownloadLoader { - cluster_path: PathBuf, - cluster_name: String, - }, - SyncCluster { - cluster_path: PathBuf, - cluster_name: String, - }, - InferPackages, - CopyCluster { - import: PathBuf, - cluster_name: String, - }, - SyncConfig { - new_path: PathBuf, - }, - Archival { - cluster_path: PathBuf, - cluster_name: String, - }, - DownloadPackage { - cluster_path: PathBuf, - package_name: String, - icon: Option, - package_id: Option, - package_version: Option, - }, - DownloadPack { - cluster_path: PathBuf, - package_name: String, - icon: Option, - package_version: String, - }, -} - -#[cfg_attr(feature = "tauri", derive(tauri_specta::Event))] -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Clone)] -pub struct IngressPayload { - pub event: IngressType, - pub ingress_uuid: Uuid, - pub fraction: Option, - pub message: String, -} - -#[cfg_attr(feature = "tauri", derive(tauri_specta::Event))] -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Clone)] -pub struct OfflinePayload { - pub offline: bool, -} - -#[cfg_attr(feature = "tauri", derive(tauri_specta::Event))] -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Clone)] -pub struct MessagePayload { - pub message: String, -} - -#[cfg_attr(feature = "tauri", derive(tauri_specta::Event))] -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Clone)] -#[serde(tag = "event")] -pub enum InternetPayload { - InstallPackage { id: String }, - InstallPack { id: String }, - InstallPath { path: PathBuf }, -} - -#[cfg_attr(feature = "tauri", derive(tauri_specta::Event))] -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Clone)] -pub struct ProcessPayload { - pub uuid: Uuid, - pub pid: u32, - pub event: ProcessPayloadType, - pub message: String, -} - -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Clone, Debug)] -#[serde(rename_all = "snake_case")] -pub enum ProcessPayloadType { - Started, - Modified, - Finished, - Logging, -} - -#[cfg_attr(feature = "tauri", derive(tauri_specta::Event))] -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Clone)] -pub struct ClusterPayload { - pub uuid: Uuid, - pub cluster_path: ClusterPath, - pub path: PathBuf, - pub name: String, - pub event: ClusterPayloadType, -} - -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Clone)] -#[serde(rename_all = "snake_case")] -pub enum ClusterPayloadType { - Created, - Inserted, - Synced, - Edited, - Deleted, -} - -#[derive(Debug, thiserror::Error)] -pub enum ProxyError { - #[error("event state was not initialized!")] - NotInitialized, - - #[error("non-existant ingress of key: {0}")] - NoIngressFound(Uuid), - - #[cfg(feature = "tauri")] - #[error("Tauri error: {0}")] - TauriError(#[from] tauri::Error), -} diff --git a/packages/core_old/src/api/proxy/send.rs b/packages/core_old/src/api/proxy/send.rs deleted file mode 100644 index 56958f56..00000000 --- a/packages/core_old/src/api/proxy/send.rs +++ /dev/null @@ -1,255 +0,0 @@ -use super::IngressId; -use crate::api::proxy::{ - ClusterPayloadType, Ingress, IngressType, InternetPayload, ProcessPayloadType, ProxyError, -}; -use crate::proxy::OfflinePayload; -use crate::store::{ClusterPath, IngressProcessType, IngressProcessor}; -use uuid::Uuid; - -#[cfg(feature = "cli")] -use crate::constants::CLI_TOTAL_INGRESS; - -#[cfg(feature = "tauri")] -use tauri_specta::Event; - -#[cfg(feature = "tauri")] -use crate::api::proxy::{ClusterPayload, IngressPayload, MessagePayload, ProcessPayload}; - -#[cfg(feature = "tauri")] -use tauri::Emitter; - -#[onelauncher_macros::memory] -pub async fn init_ingress( - ingress_type: IngressType, - total: f64, - title: &str, -) -> crate::Result { - let key = init_ingress_internal(ingress_type, total, title).await?; - IngressProcessor::add_ingress(IngressProcessType::IngressFeed, key.0).await?; - Ok(key) -} - -#[onelauncher_macros::memory] -pub async fn init_ingress_internal( - ingress_type: IngressType, - total: f64, - title: &str, -) -> crate::Result { - let proxy_state = crate::ProxyState::get().await?; - let key = IngressId(Uuid::new_v4()); - - proxy_state.ingress_feeds.write().await.insert( - key.0, - Ingress { - ingress_uuid: key.0, - message: title.to_string(), - total, - current: 0.0, - last_sent: 0.0, - ingress_type, - #[cfg(feature = "cli")] - cli: { - let pb = indicatif::ProgressBar::new(CLI_TOTAL_INGRESS); - pb.set_position(0); - pb.set_style(indicatif::ProgressStyle::default_bar().template( - "{spinner:.green}, [{elapsed_precise}] [{bar:.lime/green}] {pos}/{len} {msg}" - ).unwrap().progress_chars("#>-")); - pb - }, - }, - ); - - send_ingress(&key, 0.0, None).await?; - Ok(key) -} - -pub async fn init_or_edit_ingress( - id: Option, - ingress_type: IngressType, - total: f64, - title: &str, -) -> crate::Result { - if let Some(id) = id { - edit_ingress(&id, ingress_type, total, title).await?; - - Ok(id) - } else { - init_ingress(ingress_type, total, title).await - } -} - -pub async fn edit_ingress( - id: &IngressId, - ingress_type: IngressType, - total: f64, - title: &str, -) -> crate::Result<()> { - let proxy_state = crate::ProxyState::get().await?; - let ingress_feeds = proxy_state.ingress_feeds.write(); - - if let Some(ingress) = ingress_feeds.await.get_mut(&id.0) { - ingress.ingress_type = ingress_type; - ingress.total = total; - ingress.message = title.to_string(); - ingress.current = 0.0; - ingress.last_sent = 0.0; - #[cfg(feature = "cli")] - { - ingress.cli.reset(); - } - }; - - send_ingress(id, 0.0, None).await?; - Ok(()) -} - -#[allow(unused_variables)] -#[tracing::instrument(level = "debug")] -#[onelauncher_macros::memory] -pub async fn send_ingress( - key: &IngressId, - increment: f64, - message: Option<&str>, -) -> crate::Result<()> { - let proxy_state = crate::ProxyState::get().await?; - let mut ingress = proxy_state.ingress_feeds.write().await; - - let Some(ingress) = ingress.get_mut(&key.0) else { - return Err(ProxyError::NoIngressFound(key.0).into()); - }; - - ingress.current += increment; - let display = ingress.current / ingress.total; - let display_conv = if display >= 1.0 { None } else { Some(display) }; - - if f64::abs(display - ingress.last_sent) > 0.005 { - #[cfg(feature = "cli")] - #[allow(clippy::cast_possible_truncation)] - { - ingress - .cli - .set_message(message.map_or_else(|| ingress.message.clone(), ToString::to_string)); - ingress - .cli - .set_position((display * CLI_TOTAL_INGRESS as f64).round() as u64); - } - - #[cfg(feature = "tauri")] - proxy_state - .app - .emit( - IngressPayload::NAME, - IngressPayload { - fraction: display_conv, - message: message.unwrap_or(&ingress.message).to_string(), - event: ingress.ingress_type.clone(), - ingress_uuid: ingress.ingress_uuid, - }, - ) - .map_err(ProxyError::from)?; - - ingress.last_sent = display; - } - - Ok(()) -} - -pub async fn send_message(message: &str) -> crate::Result<()> { - #[cfg(feature = "tauri")] - { - let proxy_state = crate::ProxyState::get().await?; - proxy_state - .app - .emit( - MessagePayload::NAME, - MessagePayload { - message: message.to_string(), - }, - ) - .map_err(ProxyError::from)?; - } - - tracing::warn!("{}", message); - Ok(()) -} - -pub async fn send_offline(offline: bool) -> crate::Result<()> { - #[cfg(feature = "tauri")] - { - let proxy_state = crate::ProxyState::get().await?; - proxy_state - .app - .emit(OfflinePayload::NAME, OfflinePayload { offline }) - .map_err(ProxyError::from)?; - } - - Ok(()) -} - -pub async fn send_internet(internet: InternetPayload) -> crate::Result<()> { - tracing::debug!("operation {}", serde_json::to_string(&internet)?); - #[cfg(feature = "tauri")] - { - let proxy_state = crate::ProxyState::get().await?; - proxy_state - .app - .emit(InternetPayload::NAME, internet) - .map_err(ProxyError::from)?; - } - - Ok(()) -} - -pub async fn send_process( - uuid: Uuid, - pid: u32, - event: ProcessPayloadType, - message: &str, -) -> crate::Result<()> { - #[cfg(feature = "tauri")] - { - let proxy_state = crate::ProxyState::get().await?; - proxy_state - .app - .emit( - ProcessPayload::NAME, - ProcessPayload { - uuid, - pid, - event, - message: message.to_string(), - }, - ) - .map_err(ProxyError::from)?; - } - - Ok(()) -} - -pub async fn send_cluster( - uuid: Uuid, - cluster_path: &ClusterPath, - name: &str, - event: ClusterPayloadType, -) -> crate::Result<()> { - #[cfg(feature = "tauri")] - { - let path = cluster_path.full_path().await?; - let proxy_state = crate::ProxyState::get().await?; - proxy_state - .app - .emit( - ClusterPayload::NAME, - ClusterPayload { - uuid, - cluster_path: cluster_path.clone(), - path, - name: name.to_string(), - event, - }, - ) - .map_err(ProxyError::from)?; - } - - Ok(()) -} diff --git a/packages/core_old/src/api/proxy/utils.rs b/packages/core_old/src/api/proxy/utils.rs deleted file mode 100644 index 4f37b625..00000000 --- a/packages/core_old/src/api/proxy/utils.rs +++ /dev/null @@ -1,78 +0,0 @@ -use futures::prelude::*; - -use crate::proxy::send::send_ingress; -use crate::proxy::IngressId; - -#[macro_export] -macro_rules! count { - () => (0usize); - ( $x:tt $($xs:tt)* ) => (1usize + $crate::count!($($xs)*)); -} - -#[macro_export] -macro_rules! ingress_join { - ($key:expr, $total:expr, $msg:expr; $($task:expr $(,)?)+) => { - { - let key = $key; - let msg: Option<&str> = $msg; - let num_futures = $crate::count!($($task)*); - let i = $total / num_futures as f64; - - paste::paste! { - $( let [ ] = { - { - let key = key.clone(); - let msg = msg.clone(); - async move { - let res = $task.await; - if let Some(key) = key { - $crate::api::proxy::send::send_ingress(key, i, msg).await?; - } - res - } - } - };)+ - } - - paste::paste! { - tokio::try_join! ( - $( [ ] ),+ - ) - } - } - }; -} - -/// TryStreamExt concurrently for Ingress feeds -#[tracing::instrument(skip(stream, f))] -#[onelauncher_macros::memory] -pub async fn ingress_try_for_each( - stream: I, - limit: Option, - key: Option<&IngressId>, - total: f64, - num_futs: usize, - message: Option<&str>, - f: F, -) -> crate::Result<()> -where - I: futures::TryStreamExt + TryStream, - F: FnMut(T) -> Fut + Send, - Fut: Future> + Send, - T: Send, -{ - let mut f = f; - stream - .try_for_each_concurrent(limit, |item| { - let f = f(item); - async move { - f.await?; - if let Some(key) = key { - send_ingress(key, total / (num_futs as f64), message).await?; - } - - Ok(()) - } - }) - .await -} diff --git a/packages/core_old/src/api/settings.rs b/packages/core_old/src/api/settings.rs deleted file mode 100644 index e7ff0070..00000000 --- a/packages/core_old/src/api/settings.rs +++ /dev/null @@ -1,205 +0,0 @@ -//! Settings Management - -use crate::proxy::send::{init_ingress, send_ingress}; -use crate::store::{Clusters, Directories, Settings}; -use crate::State; -use onelauncher_utils::io; -use std::path::{Path, PathBuf}; -use tokio::sync::RwLock; - -/// gets a [`Settings`] object state. -#[tracing::instrument] -pub async fn get() -> crate::Result { - let state = State::get().await?; - let settings = state.settings.read().await; - Ok(settings.clone()) -} - -/// sets a [`Settings`] object state. -#[tracing::instrument] -pub async fn set(settings: Settings) -> crate::Result<()> { - let state = State::get().await?; - - if settings.config_dir != state.settings.read().await.config_dir { - return Err(anyhow::anyhow!("cannot change config directory as a setting").into()); - } - - let (io, fetch) = async { - let read = state.settings.read().await; - ( - settings.max_async_io_operations != read.max_async_io_operations, - settings.max_async_fetches != read.max_async_fetches, - ) - } - .await; - - let discord_rpc = { - let read = state.settings.read().await; - settings.disable_discord != read.disable_discord - }; - - { - *state.settings.write().await = settings; - } - - if discord_rpc { - state.discord_rpc.clear(true).await?; - } - - if io { - state.reset_io_semaphore().await; - } - - if fetch { - state.reset_fetch_semaphore().await; - } - - State::sync().await?; - Ok(()) -} - -/// sets the config and caches directory, this can have side effects. -#[allow(clippy::too_many_lines)] -pub async fn set_directory(new: PathBuf) -> crate::Result<()> { - tracing::trace!("changing config directory to {}", new.display()); - - if !new.is_dir() { - return Err( - anyhow::anyhow!("new config directory {} is not a folder!", new.display()).into(), - ); - } - - if !is_writable(new.clone()).await? { - return Err( - anyhow::anyhow!("new config directory {} is not writeable", new.display()).into(), - ); - } - - let ingress = init_ingress( - crate::IngressType::SyncConfig { - new_path: new.clone(), - }, - 100.0, - "syncing and changing configuration directory", - ) - .await?; - - tracing::trace!("changing config directory, overriding state write access"); - let mut state_write = State::get_and_write().await?; - let old = state_write.directories.config_dir.read().await.clone(); - - tracing::trace!("resetting file watcher after changing config directory"); - let fs_watcher = crate::utils::watcher::initialize_watcher()?; - state_write.watcher = RwLock::new(fs_watcher); - - tracing::trace!("collecting all config files to be transfered"); - let mut cfg_entries = io::read_dir(&old).await?; - let across_filesystems = is_different_fs(&old, &new); - let mut entries = vec![]; - let mut cleanable = vec![]; - - while let Some(e) = cfg_entries - .next_entry() - .await - .map_err(|err| io::IOError::with_path(err, &old))? - { - let epath = e.path(); - if let Some(file_name) = epath.file_name() { - if file_name == crate::constants::CLUSTERS_FOLDER - || file_name == crate::constants::METADATA_FOLDER - { - if across_filesystems { - entries.extend(crate::package::import::sub(&epath, false).await?); - cleanable.push(epath.clone()); - } else { - entries.push(epath.clone()); - } - } - } - } - - tracing::trace!("transferring configuration files"); - let io_semaphore = &state_write.io_semaphore; - let nentries = entries.len() as f64; - for epath in entries { - let relpath = epath - .strip_prefix(&old) - .map_err(|err| anyhow::anyhow!("failed to strip prefix {err}"))?; - let newpath = new.join(relpath); - if across_filesystems { - crate::utils::http::copy(&epath, &newpath, io_semaphore).await?; - } else { - io::rename(epath.clone(), newpath.clone()).await?; - } - - send_ingress(&ingress, 80.0 * (1.0 / nentries), None).await?; - } - - tracing::trace!("re-setting configuration settings"); - let settings = { - let mut settings = state_write.settings.write().await; - settings.config_dir = Some(new.clone()); - - tracing::trace!("updating java paths and keys"); - for j in settings.java_versions.keys() { - if let Some(java) = settings.java_versions.get_mut(&j) { - if let Ok(relpath) = PathBuf::from(java.path.clone()).strip_prefix(&old) { - java.path = new.join(relpath).to_string_lossy().to_string(); - } - } - } - - tracing::trace!("re-syncing settings file"); - settings - .sync(&state_write.directories.settings_file()) - .await?; - settings.clone() - }; - - tracing::trace!("re-initializing the config directory"); - state_write.directories = Directories::initalize(&settings)?; - let cleanable_nentries = cleanable.len(); - if cleanable_nentries > 0 { - tracing::trace!("deleting old config files"); - } - for ce in cleanable { - io::remove_dir_all(ce).await?; - send_ingress(&ingress, 10.0 * (1.0 / cleanable_nentries as f64), None).await?; - } - - tracing::trace!("re-resetting file watching system"); - let mut file_watcher = crate::utils::watcher::initialize_watcher()?; - state_write.clusters = - RwLock::new(Clusters::initialize(&state_write.directories, &mut file_watcher).await?); - state_write.watcher = RwLock::new(file_watcher); - send_ingress(&ingress, 10.0, None).await?; - - tracing::info!( - "successfully switched configuration directory to {}", - new.display() - ); - - Ok(()) -} - -/// checks if two paths are on different data storage devices -fn is_different_fs(first: &Path, second: &Path) -> bool { - let first_root = first.components().next(); - let second_root = second.components().next(); - first_root != second_root -} - -/// checks if a [`PathBuf`] is writable or locked. -pub async fn is_writable(path: PathBuf) -> crate::Result { - let tmp = path.join(".tmp"); - match io::write(tmp.clone(), "test").await { - Ok(()) => { - io::remove_file(tmp).await?; - Ok(true) - } - Err(e) => { - tracing::error!("failed to write to config directory: {}", e); - Ok(false) - } - } -} diff --git a/packages/core_old/src/constants.rs b/packages/core_old/src/constants.rs deleted file mode 100644 index 515cf5f5..00000000 --- a/packages/core_old/src/constants.rs +++ /dev/null @@ -1,118 +0,0 @@ -//! **`OneLauncher` Constants** -//! -//! Public constant variables and strings that are used throughout the launcher. -//! Can be shared publically, any secrets should be stored in [`crate::store::credentials`]. - -// =========== Core Metadata =========== -/// The name of the launcher. -pub const NAME: &str = "OneLauncher"; -/// The version of the launcher (from `../Cargo.toml` env). -pub const VERSION: &str = env!("CARGO_PKG_VERSION"); - -// =========== Default Ingress =========== -/// The amount of attempts to fetch a file before a request fails. -pub const FETCH_ATTEMPTS: usize = 3; -/// The total ingress capacity of the CLI, provided to [`indicatif`]. -#[cfg(feature = "cli")] -pub const CLI_TOTAL_INGRESS: u64 = 1000; -/// The newline which we replace in metadata files. -pub const DUMMY_REPLACE_NEWLINE: &str = "\n"; - -// =========== Authentication =========== -/// Our Discord RPC client ID. -pub const DISCORD_RPC_CLIENT_ID: &str = "1274084193193955408"; -/// Our Microsoft client ID. -pub const MICROSOFT_CLIENT_ID: &str = "9eac3a4e-8cdd-43ef-863e-49cd601b1f03"; -/// Mojang/Microsoft client ID. -pub const MINECRAFT_CLIENT_ID: &str = "00000000402b5328"; -/// Mojang/Microsoft login redirect URI. -pub const MINECRAFT_REDIRECT_URL: &str = "https://login.live.com/oauth20_desktop.srf"; -/// Mojang/Microsoft login xboxlive scopes to get tokens. -pub const MINECRAFT_SCOPES: &str = "service::user.auth.xboxlive.com::MBI_SSL"; - -// =========== API =========== -// !!! URLS must NOT have a trailing slash. !!! -/// The Modrinth API base url. -pub const MODRINTH_API_URL: &str = "https://api.modrinth.com/v2"; -/// The Modrinth V3 API base url. Used for things like fetching information about organizations. -pub const MODRINTH_V3_API_URL: &str = "https://api.modrinth.com/v3"; -/// The Curseforge API base url. -pub const CURSEFORGE_API_URL: &str = "https://api.curseforge.com"; -/// The Curseforge API key. Reserved for use in OneLauncher projects only. Do not use in other projects without permission from the OneLauncher team. -pub const CURSEFORGE_API_KEY: &str = "$2a$10$6utA1UNSmFPrE/Lh7b7ndeeGmiOkjKNY8kpFB0fsmE/d42ZAfFgCe"; -/// The Minecraft game ID on Curseforge. -pub const CURSEFORGE_GAME_ID: u32 = 432; -/// Our metadata API base url. -pub const METADATA_API_URL: &str = "https://meta.polyfrost.org"; -/// Featured packages API url. -pub const FEATURED_PACKAGES_URL: &str = "https://polyfrost.org/meta/onelauncher/featured.json"; -/// / API base url. -pub const MCLOGS_API_URL: &str = "https://api.mclo.gs/1"; -/// https://skyclient.co/ metadata base url. -pub const SKYCLIENT_BASE_URL: &str = "https://raw.githubusercontent.com/SkyblockClient/SkyblockClient-REPO/refs/heads/main/v1"; - -// =========== Paths =========== -/// The public `settings.json` file used to store the global [`Settings`] state. -/// -/// [`Settings`]: crate::store::Settings -pub const SETTINGS_FILE: &str = "settings.json"; -/// The public `cluster.json` file used to store the global [`Clusters`] state. -/// -/// [`Clusters`]: crate::store::Clusters -pub const CLUSTER_FILE: &str = "cluster.json"; -/// The public `authentication.json` file used to store the global [`MinecraftAuth`] state. -/// -/// [`MinecraftAuth`]: crate::store::MinecraftAuth -pub const AUTH_FILE: &str = "authentication.json"; -/// The public `processor.json` file used to store the global [`Processor`] state. -/// -/// [`Processor`]: crate::store::Processor -pub const PROCESSOR_FILE: &str = "processor.json"; - -/// The current [`Settings`] format version, bumped for breaking changes. -/// If updated, a config file migration logic **NEEDS** to be implemented. -/// -/// [`Settings`]: crate::store::Settings -pub const CURRENT_SETTINGS_FORMAT_VERSION: u32 = 1; - -/// The constant core caches folder. -pub const CACHES_FOLDER: &str = "caches"; -/// The constant core crash reports folder. -pub const CRASH_PATH: &str = "crash-reports"; -/// The constant core clusters folder. -pub const CLUSTERS_FOLDER: &str = "clusters"; -/// The constant core metadata folder. -pub const METADATA_FOLDER: &str = "metadata"; - -// =========== Hacky Mojang-spec OS constants =========== -#[cfg(target_os = "windows")] -pub const TARGET_OS: &str = "windows"; - -#[cfg(target_os = "macos")] -pub const TARGET_OS: &str = "osx"; - -#[cfg(target_os = "linux")] -pub const TARGET_OS: &str = "linux"; - -#[cfg(target_arch = "x86")] -pub const NATIVE_ARCH: &str = "32"; - -#[cfg(target_arch = "x86_64")] -pub const NATIVE_ARCH: &str = "64"; - -#[cfg(all(not(target_arch = "x86_64"), not(target_arch = "x86")))] -pub const NATIVE_ARCH: &str = "64"; - -// =========== Platform =========== -/// The default java executable on Windows or anything else. -pub const JAVA_BIN: &str = if cfg!(windows) { "javaw.exe" } else { "java" }; - -/// The bit width on x64 or x32 architectures. -pub const ARCH_WIDTH: &str = if cfg!(target_pointer_width = "64") { - "64" -} else { - "32" -}; - -/// Evaluates to `\r\n` on windows, and `\n` on everything else. -pub const LINE_ENDING: &str = if cfg!(windows) { "\r\n" } else { "\n" }; diff --git a/packages/core_old/src/error.rs b/packages/core_old/src/error.rs deleted file mode 100644 index b701b997..00000000 --- a/packages/core_old/src/error.rs +++ /dev/null @@ -1,180 +0,0 @@ -//! **`OneLauncher` Errors** -//! -//! Public wrappers around common `OneLauncher` errors. - -use tracing_error::InstrumentError; - -/// A standardized [`thiserror::Error`] type that is used across the launcher -#[derive(thiserror::Error, Debug)] -pub enum ErrorKind { - /// Wrapper around [`tokio::sync::AcquireError`] to handle sempahore errors. - #[error("failed to acquire a tokio semaphore: {0}")] - AcquireError(#[from] tokio::sync::AcquireError), - - /// Wrapper around [`tokio::sync::oneshot::error::RecvError`] to handle recieve errors. - #[error("failed to handle async reciever: {0}")] - RecvError(#[from] tokio::sync::oneshot::error::RecvError), - - /// Wrapper around [`tokio::task::JoinError`] to handle tokio join handle errors. - #[error("failed to handle an async task: {0}")] - JoinError(#[from] tokio::task::JoinError), - - /// Wrapper around [`flate2::CompressError`] to handle flate compression errors. - #[error("failed to compress a file with flate: {0}")] - FlateError(#[from] flate2::CompressError), - - /// Wrapper around [`flate2::DecompressError`] to handle flate decompression errors. - #[error("failed to decompress a file with flate: {0}")] - DeflateError(#[from] flate2::DecompressError), - - /// Wrapper around [`uuid::Error`] to handle UUID parsing errors. - #[error("failed to parse and handle uuids: {0}")] - UUIDError(#[from] uuid::Error), - - /// Wrapper around [`interpulse::Error`] to handle interpulse errors. - #[error("failed to parse and handle metadata: {0}")] - MetadataError(#[from] interpulse::Error), - - /// Wrapper around [`serde_json::Error`] to handle Serde JSON parsing errors. - #[error("failed to manage serialization of json: {0}")] - SerdeError(#[from] serde_json::Error), - - /// Wrapper around [`serde_ini::de::Error`] to handle Serde INI parsing errors. - #[error("failed to manage deserialization of ini: {0}")] - INIError(#[from] serde_ini::de::Error), - - /// Wrapper around [`reqwest::Error`] to handle HTTP errors. - #[error("failed to establish an http connection: {0}")] - HTTPError(#[from] reqwest::Error), - - /// Wrapper around [`zip::result::ZipError`] to handle zip errors. - #[error("failed to manage zip files: {0}")] - ZipError(#[from] zip::result::ZipError), - - /// Wrapper around [`async_zip::error::ZipError`] to handle async zip errors. - #[error("failed to manage zip files asynchronously: {0}")] - AsyncZipError(#[from] async_zip::error::ZipError), - - /// Wrapper around [`anyhow::Error`] to handle generic errors. - #[error(transparent)] - AnyhowError(#[from] anyhow::Error), - - /// Wrapper around [`chrono::ParseError`] to handle date parsing errors. - #[error("failed to parse and handle datetime: {0}")] - ChronoError(#[from] chrono::ParseError), - - /// Wrapper around [`crate::state::DirectoryError`] to handle directory errors. - #[error("failed to get directory: {0}")] - DirectoryError(#[from] crate::store::DirectoryError), - - /// Wrapper around [`crate::api::proxy::ProxyError`] to handle ingress errors. - #[error("failed to handle api communication: {0}")] - ProxyError(#[from] crate::api::proxy::ProxyError), - - /// Wrapper around [`crate::store::MinecraftAuthError`] to handle Minecraft auth errors. - #[error("failed to authenticate with microsoft: {0}")] - AuthError(#[from] crate::store::MinecraftAuthError), - - /// Wrapper around [`crate::api::cluster::create::CreateClusterError`] to handle Cluster creation errors. - #[error("failed to create clusters: {0}")] - CreateClusterError(#[from] crate::api::cluster::create::CreateClusterError), - - /// Wrapper around [`notify:Error`] to handle file watching errors. - #[error("failed to watch file {0}")] - NotifyError(#[from] notify::Error), - - /// Wrapper around [`regex::Error`] to handle `RegExp` errors. - #[error("string verification with regex failed: {0}")] - RegexError(#[from] regex::Error), - - /// Wrapper around [`reqwest::header::ToStrError`] to handle header conversion errors. - #[error("failed to convert header to string: {0}")] - HeaderToStrError(#[from] reqwest::header::ToStrError), - - /// Wrapper around [`async_tungstenite::tungstenite::Error`] to handle WebSocket errors. - #[error("failed to handle websocket connection: {0}")] - WebSocketError(#[from] async_tungstenite::tungstenite::Error), - - /// Indicates an error with SHA checksum. - #[error("checksum {0} did not match {1}!")] - HashError(String, String), - - /// Wrapper around [`onelauncher_utils::io::IOError`] to handle wrapped `std::io` errors. - #[error("error handling IO operations: {0}")] - IOError(#[from] onelauncher_utils::io::IOError), - - /// Wrapper around [`std::io::Error`] to handle non-wrapped `std::io` errors. - #[error("error handling standard IO operations: {0}")] - StdIOError(#[from] std::io::Error), - - /// Wrapper around [`crate::utils::java::JavaError`] to handle java errors. - #[error("failed to manage java installations: {0}")] - JavaError(#[from] crate::utils::java::JavaError), - - /// Wrapper around [`crate::store::StrongholdError`] to handle stronghold errors. - #[error("failed to manage credentials with IOTA stronghold: {0}")] - StrongholdError(#[from] crate::store::StrongholdError), - - /// Wrapper around [`url::ParseError`] to handle URL parsing errors - #[error("failed to parse and handle URLs: {0}")] - URLError(#[from] url::ParseError), - - /// Wrapper around [`tauri::Error`] to handle Tauri errors when the feature flag is enabled - #[cfg(feature = "tauri")] - #[error("error handling tauri: {0}")] - TauriError(#[from] tauri::Error), -} - -/// A safe, accessible error structure wrapper around [`ErrorKind`] for use with [`tracing_error`]. -#[derive(Debug)] -pub struct Error { - /// The raw, inner [`ErrorKind`], wrapped in an [`Arc`]. - pub raw: std::sync::Arc, - /// The traced [`ErrorKind`] ([`tracing_error`]) to be parsed and logged. - pub source: tracing_error::TracedError>, -} - -impl From for String { - /// Converts an [`Error`] into a [`String`] value. - fn from(value: Error) -> Self { - value.to_string() - } -} - -impl std::error::Error for Error { - /// Get the source for the inner traced [`Error`]. - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - self.source.source() - } -} - -impl std::fmt::Display for Error { - /// Format the [`Error`] and output it into the formatter buffer. - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(fmt, "{}", self.source) - } -} - -impl> From for Error { - /// Converts an [`ErrorKind`] error into a traced [`Error`]. - /// Useful for reducing error mapping and standardizing error types. - fn from(source: E) -> Self { - let error = Into::::into(source); - let boxed_error = std::sync::Arc::new(error); - - Self { - raw: boxed_error.clone(), - source: boxed_error.in_current_span(), - } - } -} - -impl ErrorKind { - /// Converts the current [`ErrorKind`] into a traced [`Error`]. - pub fn as_error(self) -> Error { - self.into() - } -} - -/// Alias for a [`core::result::Result`] with the error type [`Error`]. -pub type Result = anyhow::Result; diff --git a/packages/core_old/src/game/arguments.rs b/packages/core_old/src/game/arguments.rs deleted file mode 100644 index 90fc86d4..00000000 --- a/packages/core_old/src/game/arguments.rs +++ /dev/null @@ -1,361 +0,0 @@ -//! Programatic Minecraft argument parser which replaces variables in arguments - -use std::collections::{HashMap, HashSet}; -use std::io::prelude::BufRead; -use std::io::BufReader; -use std::path::Path; - -use interpulse::api::minecraft::{Argument, ArgumentValue, Library, VersionType}; -use interpulse::api::modded::SidedDataEntry; -use interpulse::utils::get_path_from_artifact; - -use crate::constants::DUMMY_REPLACE_NEWLINE; -use crate::store::{Memory, MinecraftCredentials, Resolution}; -use onelauncher_utils::io::IOError; -use onelauncher_utils::platform::classpath_separator; - -#[allow(clippy::too_many_arguments)] -pub fn java_arguments( - arguments: Option<&[Argument]>, - natives_path: &Path, - libraries_path: &Path, - classpaths: &str, - version: &str, - memory: Memory, - custom_args: Vec, - java_arch: &str, -) -> crate::Result> { - let mut parsed = Vec::new(); - if let Some(args) = arguments { - parse_arguments( - args, - &mut parsed, - |a| { - parse_java_argument( - a.to_string(), - natives_path, - libraries_path, - classpaths, - version, - java_arch, - ) - }, - java_arch, - )?; - } else { - parsed.push(format!( - "-Djava.library.path={}", - onelauncher_utils::io::canonicalize(natives_path) - .map_err(|_| anyhow::anyhow!( - "specified natives path {} not found", - natives_path.to_string_lossy() - ))? - .to_string_lossy() - )); - parsed.push("-cp".to_string()); - parsed.push(classpaths.to_string()); - } - - parsed.push(format!("-Xmx{}M", memory.maximum)); - parsed.push(format!("-Xms{}M", memory.minimum)); - - for arg in custom_args { - if !arg.is_empty() { - parsed.push(arg); - } - } - - Ok(parsed) -} - -#[allow(clippy::too_many_arguments)] -pub fn minecraft_arguments( - args: Option<&[Argument]>, - legacy_args: Option<&str>, - creds: &MinecraftCredentials, - version: &str, - asset_index: &str, - game_directory: &Path, - assets_directory: &Path, - version_type: VersionType, - resolution: Resolution, - java_arch: &str, -) -> crate::Result> { - if let Some(args) = args { - let mut parsed = Vec::new(); - parse_arguments( - args, - &mut parsed, - |arg| { - parse_minecraft_argument( - arg, - &creds.access_token, - &creds.username, - creds.id, - version, - asset_index, - game_directory, - assets_directory, - version_type, - resolution, - ) - }, - java_arch, - )?; - - Ok(parsed) - } else if let Some(legacy_args) = legacy_args { - let mut parsed = Vec::new(); - for arg in legacy_args.split(' ') { - parsed.push(parse_minecraft_argument( - &arg.replace(' ', DUMMY_REPLACE_NEWLINE), - &creds.access_token, - &creds.username, - creds.id, - version, - asset_index, - game_directory, - assets_directory, - version_type, - resolution, - )?); - } - - Ok(parsed) - } else { - Ok(Vec::new()) - } -} - -pub fn processor_arguments>( - libraries_path: &Path, - args: &[T], - data: &HashMap, -) -> crate::Result> { - let mut parsed = Vec::new(); - - for arg in args { - let a = &arg.as_ref()[1..arg.as_ref().len() - 1]; - if arg.as_ref().starts_with('{') { - if let Some(entry) = data.get(a) { - parsed.push(if entry.client.starts_with('[') { - get_library( - libraries_path, - &entry.client[1..entry.client.len() - 1], - true, - )? - } else { - entry.client.clone() - }); - } - } else if arg.as_ref().starts_with('[') { - parsed.push(get_library(libraries_path, a, true)?); - } else { - parsed.push(arg.as_ref().to_string()); - } - } - - Ok(parsed) -} - -pub async fn main_class(path: String) -> crate::Result> { - let main_class = tokio::task::spawn_blocking(move || { - let zipfs = std::fs::File::open(&path).map_err(|e| IOError::with_path(e, &path))?; - let mut archive = zip::ZipArchive::new(zipfs) - .map_err(|_| anyhow::anyhow!("failed to read main class {}", path))?; - let file = archive - .by_name("META-INF/MANIFEST.MF") - .map_err(|_| anyhow::anyhow!("failed to read manifest {}", path))?; - - let zip_reader = BufReader::new(file); - for line in zip_reader.lines() { - let mut line = line.map_err(IOError::from)?; - line.retain(|c| !c.is_whitespace()); - - if line.starts_with("Main-Class:") { - if let Some(class) = line.split(':').nth(1) { - return Ok(Some(class.to_string())); - } - } - } - - Ok::, crate::Error>(None) - }) - .await??; - - Ok(main_class) -} - -#[allow(clippy::too_many_arguments)] -fn parse_minecraft_argument( - argument: &str, - access_token: &str, - username: &str, - uuid: uuid::Uuid, - version: &str, - asset_index: &str, - game_directory: &Path, - assets_directory: &Path, - version_type: VersionType, - resolution: Resolution, -) -> crate::Result { - Ok(argument - .replace("${accessToken}", access_token) - .replace("${auth_access_token}", access_token) - .replace("${auth_session}", access_token) - .replace("${auth_player_name}", username) - .replace("${auth_xuid}", "0") // TODO: add auth xuid - .replace("${auth_uuid}", &uuid.simple().to_string()) - .replace("${uuid}", &uuid.simple().to_string()) - .replace("${clientid}", crate::constants::MICROSOFT_CLIENT_ID) - .replace("${user_properties}", "{}") - .replace("${user_type}", "msa") - .replace("${version_name}", version) - .replace("${assets_index_name}", asset_index) - .replace( - "${game_directory}", - &onelauncher_utils::io::canonicalize(game_directory)?.to_string_lossy(), - ) - .replace( - "${assets_root}", - &onelauncher_utils::io::canonicalize(assets_directory)?.to_string_lossy(), - ) - .replace( - "${game_assets}", - &onelauncher_utils::io::canonicalize(assets_directory)?.to_string_lossy(), - ) - .replace("${version_type}", version_type.as_str()) - .replace("${resolution_width}", &resolution.0.to_string()) - .replace("${resolution_height}", &resolution.1.to_string())) -} - -#[allow(clippy::too_many_arguments)] -fn parse_java_argument( - mut argument: String, - natives_path: &Path, - libraries_path: &Path, - classpaths: &str, - version: &str, - java_arch: &str, -) -> crate::Result { - argument.retain(|c| !c.is_whitespace()); - Ok(argument - .replace( - "${natives_directory}", - &onelauncher_utils::io::canonicalize(natives_path)?.to_string_lossy(), - ) - .replace( - "${library_directory}", - &onelauncher_utils::io::canonicalize(libraries_path)?.to_string_lossy(), - ) - .replace("${classpath_separator}", classpath_separator(java_arch)) - .replace("${launcher_name}", crate::constants::NAME) - .replace("${launcher_version}", crate::constants::VERSION) - .replace("${version_name}", version) - .replace("${classpath}", classpaths)) -} - -pub fn classpaths( - libraries_path: &Path, - libraries: &[Library], - client_path: &Path, - java_arch: &str, - updated: bool, -) -> crate::Result { - let mut classpaths = libraries - .iter() - .filter_map(|lib| { - if let Some(rules) = &lib.rules { - if !crate::game::rules(rules, java_arch, updated) { - return None; - } - } - - if !lib.include_in_classpath { - return None; - } - - Some(get_library(libraries_path, &lib.name, false)) - }) - .collect::, _>>()?; - - classpaths.insert( - onelauncher_utils::io::canonicalize(client_path)? - .to_string_lossy() - .to_string(), - ); - - Ok(classpaths - .into_iter() - .collect::>() - .join(classpath_separator(java_arch))) -} - -pub fn get_classpath_library>( - libraries_path: &Path, - libraries: &[T], - java_arch: &str, -) -> crate::Result { - let classpaths = libraries - .iter() - .map(|lib| get_library(libraries_path, lib.as_ref(), false)) - .collect::, _>>()?; - - Ok(classpaths.join(classpath_separator(java_arch))) -} - -pub fn get_library( - libraries_path: &Path, - library: &str, - error_exist: bool, -) -> crate::Result { - let mut path = libraries_path.to_path_buf(); - path.push(get_path_from_artifact(library)?); - - if !path.exists() && error_exist { - return Ok(path.to_string_lossy().to_string()); - } - - let path = &onelauncher_utils::io::canonicalize(&path)?; - - Ok(path.to_string_lossy().to_string()) -} - -fn parse_arguments( - args: &[Argument], - parsed: &mut Vec, - parse_function: ParseFn, - java_arch: &str, -) -> crate::Result<()> -where - ParseFn: Fn(&str) -> crate::Result, -{ - for arg in args { - match arg { - Argument::Normal(a) => { - let p = parse_function(&a.replace(' ', DUMMY_REPLACE_NEWLINE))?; - for split_p in p.split(DUMMY_REPLACE_NEWLINE) { - parsed.push(split_p.to_string()); - } - } - Argument::Ruled { rules, value } => { - if super::rules(rules, java_arch, true) { - match value { - ArgumentValue::Single(arg) => { - parsed.push(parse_function(&arg.replace(' ', DUMMY_REPLACE_NEWLINE))?); - } - ArgumentValue::Many(args) => { - for arg in args { - parsed.push(parse_function( - &arg.replace(' ', DUMMY_REPLACE_NEWLINE), - )?); - } - } - } - } - } - } - } - - Ok(()) -} diff --git a/packages/core_old/src/game/metadata.rs b/packages/core_old/src/game/metadata.rs deleted file mode 100644 index 6ebab90a..00000000 --- a/packages/core_old/src/game/metadata.rs +++ /dev/null @@ -1,349 +0,0 @@ -//! Downloads Minecraft metadata to be launched - -use futures::prelude::*; -use interpulse as ip; -use tokio::sync::OnceCell; - -use interpulse::api::minecraft::{ - Asset, AssetsIndex, Library, Os, Version as GameVersion, VersionInfo as GameVersionInfo, -}; -use interpulse::api::modded::LoaderVersion; - -use crate::proxy::send::send_ingress; -use crate::proxy::utils::ingress_try_for_each; -use crate::proxy::IngressId; -use crate::store::State; -use crate::utils::http::{fetch, fetch_json, write}; -use onelauncher_utils::io; -use onelauncher_utils::platform::OsExt; - -#[tracing::instrument(skip(st, version))] -pub async fn download_minecraft( - st: &State, - version: &GameVersionInfo, - ingress: &IngressId, - java_arch: &str, - force: bool, - updated: bool, -) -> crate::Result<()> { - tracing::info!("downloading minecraft version {}", version.id); - let assets_index = download_assets_index(st, version, Some(ingress), force).await?; - let processors = if version.processors.as_ref().is_some_and(|p| !p.is_empty()) { - 25.0 - } else { - 40.0 - }; - - tokio::try_join! { - download_client(st, version, Some(ingress), force), - download_assets(st, version.assets == "legacy", &assets_index, Some(ingress), processors, force), - download_libraries(st, version.libraries.as_slice(), &version.id, Some(ingress), processors, java_arch, force, updated) - }?; - - tracing::info!("downloaded minecraft version {}", version.id); - Ok(()) -} - -#[tracing::instrument(skip_all, fields(version = version.id.as_str(), loader = ?loader))] -#[onelauncher_macros::memory] -pub async fn download_version_info( - st: &State, - version: &GameVersion, - loader: Option<&LoaderVersion>, - force: Option, - ingress: Option<&IngressId>, -) -> crate::Result { - let version_id = loader.map_or(version.id.clone(), |it| format!("{}-{}", version.id, it.id)); - tracing::debug!("loading minecraft version info for minecraft version {version_id}"); - let path = st - .directories - .version_dir(&version_id) - .await - .join(format!("{version_id}.json")); - let result = if path.exists() && !force.unwrap_or(false) { - io::read(path) - .err_into::() - .await - .and_then(|ref it| Ok(serde_json::from_slice(it)?)) - } else { - tracing::info!( - "downloading minecraft version info for minecraft version {}", - &version_id - ); - let mut info = fetch_json( - reqwest::Method::GET, - &version.url, - None, - None, - &st.fetch_semaphore, - ) - .await?; - - if let Some(loader) = loader { - let partial: ip::api::modded::PartialVersionInfo = fetch_json( - reqwest::Method::GET, - &loader.url, - None, - None, - &st.fetch_semaphore, - ) - .await?; - info = ip::api::modded::merge_partial_version(partial, info); - } - - info.id.clone_from(&version_id); - - write(&path, &serde_json::to_vec(&info)?, &st.io_semaphore).await?; - Ok(info) - }?; - - if let Some(ingress) = ingress { - send_ingress(ingress, 5.0, None).await?; - } - - tracing::debug!("loaded minecraft version info for minecraft version {version_id}"); - Ok(result) -} - -#[tracing::instrument(skip_all)] -#[onelauncher_macros::memory] -pub async fn download_assets_index( - st: &State, - version: &GameVersionInfo, - ingress: Option<&IngressId>, - force: bool, -) -> crate::Result { - tracing::debug!("loading assets index"); - let path = st - .directories - .index_dir() - .await - .join(format!("{}.json", &version.asset_index.id)); - - let result = if path.exists() && !force { - io::read(path) - .err_into::() - .await - .and_then(|ref it| Ok(serde_json::from_slice(it)?)) - } else { - let index = fetch_json( - reqwest::Method::GET, - &version.asset_index.url, - None, - None, - &st.fetch_semaphore, - ) - .await?; - write(&path, &serde_json::to_vec(&index)?, &st.io_semaphore).await?; - tracing::info!("downloaded assets index"); - Ok(index) - }?; - - if let Some(ingress) = ingress { - send_ingress(ingress, 5.0, None).await?; - } - - tracing::debug!("loaded assets index"); - Ok(result) -} - -#[tracing::instrument(skip(st, index))] -#[onelauncher_macros::memory] -pub async fn download_assets( - st: &State, - legacy: bool, - index: &AssetsIndex, - ingress: Option<&IngressId>, - ingress_amount: f64, - force: bool, -) -> crate::Result<()> { - tracing::debug!("loading minecraft assets"); - let num_futs = index.objects.len(); - let assets = stream::iter(index.objects.iter()).map(Ok::<(&String, &Asset), crate::Error>); - - ingress_try_for_each( - assets, - None, - ingress, - ingress_amount, - num_futs, - None, - |(name, asset)| async move { - let hash = &asset.hash; - let resources = st.directories.object_dir(hash).await; - let url = format!( - "https://resources.download.minecraft.net/{sub_hash}/{hash}", - sub_hash = &hash[..2] - ); - let fetch_cell = OnceCell::::new(); - tokio::try_join! { - async { - if !resources.exists() || force { - let resource = fetch_cell.get_or_try_init(|| fetch(&url, Some(hash), &st.fetch_semaphore)).await?; - write(&resources, resource, &st.io_semaphore).await?; - tracing::trace!("fetched asset resource with hash {hash}"); - } - Ok::<_, crate::Error>(()) - }, - async { - let resources = st.directories.legacy_assets_dir().await.join( - name.replace('/', &String::from(std::path::MAIN_SEPARATOR)) - ); - - if legacy && !resources.exists() || force { - let resource = fetch_cell.get_or_try_init(|| fetch(&url, Some(hash), &st.fetch_semaphore)).await?; - write(&resources, resource, &st.io_semaphore).await?; - tracing::trace!("fetched legacy asset resource with hash {hash}"); - } - Ok::<_, crate::Error>(()) - }, - }?; - - tracing::trace!("loaded asset resource with hash {hash}"); - Ok(()) - }, - ) - .await?; - - tracing::debug!("loaded minecraft assets"); - Ok(()) -} - -#[tracing::instrument(skip(st, libraries))] -#[onelauncher_macros::memory] -#[allow(clippy::too_many_arguments)] -pub async fn download_libraries( - st: &State, - libraries: &[Library], - version: &str, - ingress: Option<&IngressId>, - ingress_amount: f64, - java_arch: &str, - force: bool, - updated: bool, -) -> crate::Result<()> { - tracing::debug!("loading minecraft libraries"); - - tokio::try_join! { - io::create_dir_all(st.directories.libraries_dir().await), - io::create_dir_all(st.directories.version_natives_dir(version).await) - }?; - - let num_files = libraries.len(); - // TODO: figure out why rustfmt won't format this (it's not a macro...) - ingress_try_for_each( - stream::iter(libraries.iter()) - .map(Ok::<&Library, crate::Error>), - None, - ingress, - ingress_amount, - num_files, - None, - |lib| async move { - if let Some(rules) = &lib.rules { - if !super::rules(rules, java_arch, updated) { - tracing::trace!("skipped library {} due to rule", &lib.name); - return Ok(()); - } - } - - if !lib.downloadable { - tracing::trace!("skipped non-downloadable library {}", &lib.name); - return Ok(()); - } - - tokio::try_join! { - async { - let artifact_path = ip::utils::get_path_from_artifact(&lib.name)?; - let path = st.directories.libraries_dir().await.join(&artifact_path); - - if path.exists() && !force { - return Ok(()); - } - - if let Some(ip::api::minecraft::LibraryDownloads { - artifact: Some(ref artifact), .. - }) = lib.downloads { - if !artifact.url.is_empty() { - let bytes = fetch(&artifact.url, Some(&artifact.sha1), &st.fetch_semaphore).await?; - write(&path, &bytes, &st.io_semaphore).await?; - tracing::trace!("fetched library {} to path {:?}", &lib.name, &path); - return Ok::<_, crate::Error>(()); - } - } - - let url = [lib.url.as_deref().unwrap_or("https://libraries.minecraft.net/"), &artifact_path].concat(); - let bytes = fetch(&url, None, &st.fetch_semaphore).await?; - write(&path, &bytes, &st.io_semaphore).await?; - tracing::trace!("fetched library {} to path {:?}", &lib.name, &path); - Ok::<_, crate::Error>(()) - }, - async { - if let Some((os_key, classifiers)) = None.or_else(|| Some(( - lib.natives.as_ref()?.get(&Os::native_arch(java_arch))?, - lib.downloads.as_ref()?.classifiers.as_ref()? - ))) { - let parsed = os_key.replace("${arch}", crate::constants::ARCH_WIDTH); - if let Some(native) = classifiers.get(&parsed) { - let data = fetch(&native.url, Some(&native.sha1), &st.fetch_semaphore).await?; - let reader = std::io::Cursor::new(&data); - if let Ok(mut archive) = zip::ZipArchive::new(reader) { - match archive.extract(st.directories.version_natives_dir(version).await) { - Ok(()) => tracing::debug!("downloaded native {}", &lib.name), - Err(err) => tracing::error!("failed to download native {}: {}", &lib.name, err) - } - } else { - tracing::error!("failed to extract native {}", &lib.name); - } - } - } - - Ok(()) - } - }?; - - tracing::debug!("loaded minecraft library {}", lib.name); - Ok(()) - } - ).await?; - - tracing::debug!("downloaded all minecraft libraries"); - Ok(()) -} - -#[tracing::instrument(skip_all)] -#[onelauncher_macros::memory] -pub async fn download_client( - st: &State, - version: &GameVersionInfo, - ingress: Option<&IngressId>, - force: bool, -) -> crate::Result<()> { - let version_id = &version.id; - tracing::debug!("loading minecraft client for minecraft version {version_id}"); - - let client = version - .downloads - .get(&ip::api::minecraft::DownloadType::Client) - .ok_or_else(|| anyhow::anyhow!("no client downloads exist for {version_id}"))?; - - let path = st - .directories - .version_dir(version_id) - .await - .join(format!("{version_id}.jar")); - - if !path.exists() || force { - let result = fetch(&client.url, Some(&client.sha1), &st.fetch_semaphore).await?; - - write(&path, &result, &st.io_semaphore).await?; - tracing::trace!("fetched minecraft client version {version_id}"); - } - - if let Some(ingress) = ingress { - send_ingress(ingress, 9.0, None).await?; - } - - tracing::debug!("downloaded client for minecraft version {version_id}"); - Ok(()) -} diff --git a/packages/core_old/src/game/mod.rs b/packages/core_old/src/game/mod.rs deleted file mode 100644 index f0ef6f18..00000000 --- a/packages/core_old/src/game/mod.rs +++ /dev/null @@ -1,544 +0,0 @@ -//! **`OneLauncher` Game** -//! -//! Manages, installs, and launches the core Minecraft game. - -use crate::prelude::JavaVersion; -use crate::processor; -use crate::proxy::send::{init_or_edit_ingress, send_ingress}; -use crate::proxy::{IngressId, IngressType}; -use crate::store::{ - self as st, Cluster, ClusterStage, MinecraftCredentials, ProcessorChild, State, -}; -use onelauncher_utils::io::{self, IOError}; - -use chrono::Utc; -use interpulse as ip; -use interpulse::api::minecraft::{RuleAction, VersionInfo}; -use std::collections::HashMap; -use std::sync::Arc; -use tokio::process::Command; -use uuid::Uuid; - -mod arguments; -pub mod metadata; - -macro_rules! data_entry { - ($dest:expr; $($name:literal: client => $client:expr, server => $server:expr;)+) => { - $(std::collections::HashMap::insert( - $dest, - String::from($name), - interpulse::api::modded::SidedDataEntry { - client: String::from($client), - server: String::from($server), - }, - );)+ - } -} - -#[tracing::instrument(skip(cluster))] -#[onelauncher_macros::memory] -pub async fn install_minecraft( - cluster: &Cluster, - old_ingress: Option, - repair: bool, -) -> crate::Result<()> { - // let should_sync = old_ingress.is_some(); - let ingress = init_or_edit_ingress( - old_ingress, - IngressType::DownloadLoader { - cluster_name: cluster.meta.name.clone(), - cluster_path: cluster.get_full_path().await?, - }, - 100.0, - "downloading minecraft loader", - ) - .await?; - - crate::api::cluster::edit(&cluster.cluster_path(), |clus| { - clus.stage = ClusterStage::Downloading; - - async { Ok(()) } - }) - .await?; - - State::sync().await?; - - // if should_sync { - // let path = cluster.cluster_path(); - // tokio::task::spawn(async move { - // Cluster::sync_packages(&path).await - // }); - // } - - let state = State::get().await?; - let instance_path = &io::canonicalize(cluster.get_full_path().await?)?; - let metadata = state.metadata.read().await; - - let version_manifest = metadata.minecraft.clone(); - let versions = version_manifest - .ok_or_else(|| anyhow::anyhow!("couldn't get minecraft manifest"))? - .versions; - - let version_idx = versions - .iter() - .position(|g| g.id == cluster.meta.mc_version) - .ok_or_else(|| { - anyhow::anyhow!("invalid minecraft game version {}", cluster.meta.mc_version) - })?; - - let version = &versions[version_idx]; - let updated = version_idx <= versions.iter().position(|g| g.id == "22w16a").unwrap_or(0); // LWJGL patching - let version_jar = cluster - .meta - .loader_version - .as_ref() - .map_or(version.id.clone(), |g| { - format!("{}-{}", version.id.clone(), g.id.clone()) - }); - let mut version_info = metadata::download_version_info( - &state, - version, - cluster.meta.loader_version.as_ref(), - Some(repair), - Some(&ingress), - ) - .await?; - // TODO: java checking and installation - let key = version_info - .java_version - .as_ref() - .map_or(8, |it| it.major_version); - let (java_version, set_java) = - if let Some(java_version) = java_version_from_cluster(cluster, &version_info).await? { - (std::path::PathBuf::from(java_version.path), false) - } else { - (crate::api::java::install_java_from_major(key).await?, true) - }; - - let java_version = crate::api::java::check_java(java_version.clone()) - .await? - .ok_or_else(|| anyhow::anyhow!("java path validation failed: {:?}", java_version))?; - - if set_java { - { - let mut settings = state.settings.write().await; - settings - .java_versions - .insert(key.to_string(), java_version.clone()); - } - State::sync().await?; - } - - metadata::download_minecraft( - &state, - &version_info, - &ingress, - &java_version.arch, - repair, - updated, - ) - .await?; - - if let Some(processors) = &version_info.processors { - let client = state - .directories - .version_dir(&version_jar) - .await - .join(format!("{version_jar}.jar")); - let libraries = state.directories.libraries_dir().await; - if let Some(ref mut data) = version_info.data { - data_entry! { - data; - "SIDE": - client => "client", - server => ""; - "MINECRAFT_JAR": - client => client.to_string_lossy(), - server => ""; - "MINECRAFT_VERSION": - client => cluster.meta.mc_version.clone(), - server => ""; - "ROOT": - client => instance_path.to_string_lossy(), - server => ""; - "LIBRARY_DIR": - client => libraries.to_string_lossy(), - server => ""; - } - - send_ingress(&ingress, 0.0, Some("running forge processors")).await?; - let total_length = processors.len(); - for (index, processor) in processors.iter().enumerate() { - if let Some(sides) = &processor.sides { - if !sides.contains(&String::from("client")) { - continue; - } - } - - let cp = onelauncher_utils::ref_owned!(cp = processor.classpath.clone() => { - cp.push(processor.jar.clone()); - }); - - let child = Command::new(&java_version.path) - .arg("-cp") - .arg(arguments::get_classpath_library( - &libraries, - &cp, - &java_version.arch, - )?) - .arg( - arguments::main_class(arguments::get_library( - &libraries, - &processor.jar, - false, - )?) - .await? - .ok_or_else(|| { - anyhow::anyhow!( - "failed to find processor main class for {}", - processor.jar - ) - })?, - ) - .args(arguments::processor_arguments( - &libraries, - &processor.args, - data, - )?) - .output() - .await - .map_err(|e| IOError::with_path(e, &java_version.path)) - .map_err(|err| anyhow::anyhow!("failed to run processor: {err}"))?; - - if !child.status.success() { - return Err(anyhow::anyhow!( - "error occured while running processor: {}", - String::from_utf8_lossy(&child.stderr) - ) - .into()); - } - - send_ingress( - &ingress, - 30.0 / total_length as f64, - Some(&format!("running forge processor {index}/{total_length}")), - ) - .await?; - } - } - } - - crate::api::cluster::edit(&cluster.cluster_path(), |clus| { - clus.stage = ClusterStage::Installed; - - async { Ok(()) } - }) - .await?; - State::sync().await?; - send_ingress(&ingress, 100.0, Some("installed minecraft successfully")).await?; - - Ok(()) -} - -#[tracing::instrument(skip_all)] -#[onelauncher_macros::memory] -#[allow(clippy::too_many_arguments)] -pub async fn launch_minecraft( - cluster: &Cluster, - java_args: &[String], - env_args: &[(String, String)], - mc_options: &[(String, String)], - post_hook: Option, - credentials: &MinecraftCredentials, - resolution: &st::Resolution, - memory: &st::Memory, - wrapper: &Option, -) -> crate::Result>> { - if cluster.stage == ClusterStage::PackDownloading || cluster.stage == ClusterStage::Downloading - { - return Err(anyhow::anyhow!("cluster is still downloading").into()); - } - - if cluster.stage != ClusterStage::Installed { - install_minecraft(cluster, None, false).await?; - } - - let state = State::get().await?; - let metadata = state.metadata.read().await; - let instance_path = cluster.get_full_path().await?; - let instance_path = &io::canonicalize(instance_path)?; - - let version_manifest = metadata.minecraft.clone(); - let versions = version_manifest - .ok_or_else(|| anyhow::anyhow!("couldn't get minecraft manifest"))? - .versions; - - let version_index = versions - .iter() - .position(|it| it.id == cluster.meta.mc_version) - .ok_or_else(|| anyhow::anyhow!("invalid game version {}", cluster.meta.mc_version))?; - - let version = &versions[version_index]; - let updated = version_index <= versions.iter().position(|x| x.id == "22w16a").unwrap_or(0); - let version_jar = cluster - .meta - .loader_version - .as_ref() - .map_or(version.id.clone(), |it| { - format!("{}-{}", version.id.clone(), it.id.clone()) - }); - let version_info = metadata::download_version_info( - &state, - version, - cluster.meta.loader_version.as_ref(), - None, - None, - ) - .await?; - let java_version = java_version_from_cluster(cluster, &version_info) - .await? - .ok_or_else(|| anyhow::anyhow!("missing java installation"))?; - let java_version = crate::api::java::check_java(java_version.path.clone().into()) - .await? - .ok_or_else(|| anyhow::anyhow!("java path invalid: {}", java_version.path))?; - let client_path = state - .directories - .version_dir(&version_jar) - .await - .join(format!("{version_jar}.jar")); - let args = version_info.arguments.clone().unwrap_or_default(); - let mut command = match wrapper { - Some(hook) => { - onelauncher_utils::ref_owned!(it = Command::new(hook) => {it.arg(&java_version.path)}) - } - None => Command::new(&java_version.path), - }; - let env_args = Vec::from(env_args); - if !state.settings.read().await.allow_parallel_running_clusters { - let existing = processor::get_uuids_by_cluster_path(cluster.cluster_path()).await?; - if let Some(uuid) = existing.first() { - return Err(anyhow::anyhow!( - "cluster {} is already running ({uuid})", - cluster.cluster_path() - ) - .into()); - } - } - - command - .args( - arguments::java_arguments( - args.get(&ip::api::minecraft::ArgumentType::Jvm) - .map(Vec::as_slice), - &state.directories.version_natives_dir(&version_jar).await, - &state.directories.libraries_dir().await, - &arguments::classpaths( - &state.directories.libraries_dir().await, - version_info.libraries.as_slice(), - &client_path, - &java_version.arch, - updated, - )?, - &version_jar, - *memory, - Vec::from(java_args), - &java_version.arch, - )? - .into_iter(), - ) - .arg(version_info.main_class.clone()) - .args( - arguments::minecraft_arguments( - args.get(&ip::api::minecraft::ArgumentType::Game) - .map(Vec::as_slice), - version_info.minecraft_arguments.as_deref(), - credentials, - &version.id, - &version_info.asset_index.id, - instance_path, - &state.directories.assets_dir().await, - version.type_, - *resolution, - &java_version.arch, - )? - .into_iter(), - ) - .current_dir(instance_path.clone()); - - // when cargo sets the `DYLD_LIBRARY_PATH`` it breaks Minecraft - #[cfg(target_os = "macos")] - if std::env::var("CARGO").is_ok() { - command.env_remove("DYLD_FALLBACK_LIBRARY_PATH"); - } - - // remove pre-existing java options, as they should be set in the cluster settings. - command.env_remove("_JAVA_OPTIONS"); - command.envs(env_args); - - // overrides `options.txt` with our settings: i can't believe it's not yaml - if !mc_options.is_empty() { - let options_path = instance_path.join("options.txt"); - let mut options_string = if options_path.exists() { - io::read_to_string(&options_path).await? - } else { - String::new() - }; - for (key, value) in mc_options { - let re = regex::Regex::new(&format!(r"(?m)^{}:.*$", regex::escape(key)))?; - if re.is_match(&options_string) { - let replaced_string = re - .replace_all(&options_string, &format!("{key}:{value}")) - .to_string(); - options_string = replaced_string; - } else { - options_string.push_str(&format!("\n{key}:{value}")); - } - } - - io::write(&options_path, options_string).await?; - } - - crate::api::cluster::edit(&cluster.cluster_path(), |clust| { - clust.meta.played_at = Some(Utc::now()); - - async { Ok(()) } - }) - .await?; - State::sync().await?; - - let mut censors = HashMap::new(); - let username = whoami::username(); - let realname = whoami::realname(); - censors.insert(format!("/{username}/"), "/{ENV_USERNAME}/".to_string()); - censors.insert(format!("\\{username}\\"), "\\{ENV_USERNAME}\\".to_string()); - censors.insert(format!("/{realname}/"), "/{ENV_REALNAME}/".to_string()); - censors.insert(format!("\\{realname}\\"), "\\{ENV_REALNAME}\\".to_string()); - censors.insert( - credentials.access_token.clone(), - "{MC_ACCESS_TOKEN}".to_string(), - ); - censors.insert(credentials.username.clone(), "{MC_USERNAME}".to_string()); - censors.insert( - credentials.id.as_simple().to_string(), - "{MC_UUID}".to_string(), - ); - censors.insert( - credentials.id.as_hyphenated().to_string(), - "{MC_UUID}".to_string(), - ); - - #[cfg(feature = "tauri")] - { - use crate::ProxyState; - let window = ProxyState::get_main_window().await?; - let settings = state.settings.read().await; - if settings.hide_on_launch { - window.minimize()?; - } - } - - if !*state.offline.read().await { - let _ = state - .discord_rpc - .set_activity(&format!("Playing {}", cluster.meta.name), true) - .await; - } - - let enable_gamemode = state.settings.read().await.enable_gamemode; - let mut state_processor = state.processor.write().await; - state_processor - .insert_process( - Uuid::new_v4(), - cluster.cluster_path(), - command, - post_hook, - censors, - Some(credentials.id), - Some(enable_gamemode), - ) - .await -} - -/// Parses an array of Minecraft library feature or OS rules. -#[tracing::instrument] -pub fn rules(rules: &[ip::api::minecraft::Rule], java_version: &str, updated: bool) -> bool { - let mut rule = rules - .iter() - .map(|r| rule(r, java_version, updated)) - .collect::>>(); - if rules - .iter() - .all(|r| matches!(r.action, RuleAction::Disallow)) - { - rule.push(Some(true)); - } - - !(rule.iter().any(|r| r == &Some(false)) || rule.iter().all(Option::is_none)) -} - -/// Parses a Minecraft library feature or OS rule. -/// Is disallowed -> Don't include it -/// Is not allowed -> Don't include it -/// Is allowed -> Include it -#[tracing::instrument] -pub fn rule(rule: &ip::api::minecraft::Rule, java_version: &str, updated: bool) -> Option { - use ip::api::minecraft::{Rule, RuleAction}; - - let result = match rule { - Rule { - os: Some(ref os), .. - } => onelauncher_utils::platform::os_rule(os, java_version, updated), - Rule { - features: Some(ref features), - .. - } => { - !features.is_demo_user.unwrap_or(true) - || features.has_custom_resolution.unwrap_or(false) - || !features.has_quick_plays_support.unwrap_or(true) - || !features.is_quick_play_multiplayer.unwrap_or(true) - || !features.is_quick_play_realms.unwrap_or(true) - || !features.is_quick_play_singleplayer.unwrap_or(true) - } - _ => return Some(true), - }; - - match rule.action { - RuleAction::Allow => { - if result { - Some(true) - } else { - Some(false) - } - } - RuleAction::Disallow => { - if result { - Some(false) - } else { - None - } - } - } -} - -pub async fn java_version_from_cluster( - cluster: &Cluster, - version_info: &VersionInfo, -) -> crate::Result> { - if let Some(java) = cluster.java.clone().and_then(|x| x.custom_version) { - Ok(Some(java)) - } else { - let key = version_info - .java_version - .as_ref() - .map_or(8, |x| x.major_version); - - let state = State::get().await?; - let settings = state.settings.read().await; - - if let Some(j) = settings.java_versions.get(&key.to_string()) { - return Ok(Some(j.clone())); - } - - Ok(None) - } -} diff --git a/packages/core_old/src/lib.rs b/packages/core_old/src/lib.rs deleted file mode 100644 index 485488a9..00000000 --- a/packages/core_old/src/lib.rs +++ /dev/null @@ -1,20 +0,0 @@ -#![allow(clippy::all)] -//! # `OneLauncher` -//! -//! A library used as a core for our launcher and Rust APIs. - -#[macro_use] -pub mod utils; - -pub mod api; -pub mod constants; -pub mod error; -pub mod game; -pub mod logger; -pub mod store; - -pub use api::proxy::{Ingress, IngressType, ProxyState}; -pub use api::*; -pub use error::*; -pub use logger::start_logger; -pub use store::{InnerPathLinux, State}; diff --git a/packages/core_old/src/logger.rs b/packages/core_old/src/logger.rs deleted file mode 100644 index 742a37ca..00000000 --- a/packages/core_old/src/logger.rs +++ /dev/null @@ -1,74 +0,0 @@ -//! **`OneLauncher` Logger** -//! -//! Public utilities for [`tracing`] logging in `OneLauncher`. -//! -//! - `RUST_LOG=xxx` is a variable which defines tracing configuration (`ERROR > WARN > INFO > DEBUG > TRACE`) -//! - `RUST_LOG="onelauncher=trace"` will show **all** mesages from `OneLauncher` and not other dependencies. -//! - `RUST_LOG="info"` will show info, warn, and error logs. -//! -//! Errors returned to [`tauri`] are displayed as tracing error logs. -//! These errors also include an attached span trace if the error is from a [`tracing_error`] and the level is info, debug, or trace. -//! Tracing errors also provide Sentry metadata to be handled. -//! To set the environment variable on `unix` you can use the following command: `RUST_LOG="xxx" {your command}` -//! -//! The default `RUST_LOG` is `onelauncher=show`, meaning only logs from `OneLauncher` will be displayed at the info, warn, and error levels. - -use tracing_appender::non_blocking::WorkerGuard; - -// TODO: refactor logging and add more env filters. - -/// Initialize the global [`tracing`] logger in development. -#[cfg(debug_assertions)] -#[must_use] -pub fn start_logger() -> Option { - use tracing_subscriber::prelude::*; - - let filter = tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| { - tracing_subscriber::EnvFilter::new("onelauncher=info,onelauncher_gui=info") - }); - - let subscriber = tracing_subscriber::registry() - .with(tracing_subscriber::fmt::layer()) - .with(filter) - .with(tracing_error::ErrorLayer::default()); - - tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); - - None -} - -/// Initialize the global [`tracing`] logger in production. -#[cfg(not(debug_assertions))] -pub fn start_logger() -> Option { - use crate::store::Directories; - use tracing_appender::rolling::{RollingFileAppender, Rotation}; - use tracing_subscriber::fmt::time::ChronoLocal; - use tracing_subscriber::prelude::*; - - let logs_dir = if let Some(directory) = Directories::logs_dir() { - directory - } else { - eprintln!("failed to start logger"); - return None; - }; - - let filter = tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("onelauncher=info")); - - let file_appender = RollingFileAppender::new(Rotation::DAILY, logs_dir, "onelauncher.log"); - let (non_blocking, guard) = tracing_appender::non_blocking(file_appender); - - let subscriber = tracing_subscriber::registry() - .with( - tracing_subscriber::fmt::layer() - .with_writer(non_blocking) - .with_ansi(false) - .with_timer(ChronoLocal::rfc_3339()), - ) - .with(filter) - .with(tracing_error::ErrorLayer::default()); - - tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); - - Some(guard) -} diff --git a/packages/core_old/src/store/clusters.rs b/packages/core_old/src/store/clusters.rs deleted file mode 100644 index 82d3f9d5..00000000 --- a/packages/core_old/src/store/clusters.rs +++ /dev/null @@ -1,802 +0,0 @@ -//! Core cluster state management - -use chrono::{DateTime, Utc}; -use futures::prelude::*; -use interpulse::api::modded::LoaderVersion; -use notify::RecommendedWatcher; -use notify_debouncer_mini::Debouncer; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::path::{Path, PathBuf}; -use std::str::FromStr; -use uuid::Uuid; - -use crate::constants::CLUSTER_FILE; -use crate::proxy::send::{send_cluster, send_message}; -use crate::proxy::ClusterPayloadType; -use crate::utils::http::{write_icon, IoSemaphore}; -use crate::utils::java::JavaVersion; -use crate::State; -use onelauncher_utils::io::{self, IOError}; - -use super::{Directories, InitHooks, Memory, PackageType, Resolution}; - -/// Core Cluster state manager with a [`HashMap`]. -pub struct Clusters(pub HashMap); - -/// Core Cluster stages used in package logic. -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Deserialize, Clone, Copy, Debug, Default, Eq, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum ClusterStage { - /// Installed with no downloads left. - Installed, - /// Downloading any sort of core metadata or mod file. - Downloading, - /// Downloading a full pack file, which is granted lower priority due to the high network demand. - PackDownloading, - /// Not installed at all. - #[default] - NotInstalled, -} - -impl ClusterStage { - #[must_use] - pub const fn as_str(&self) -> &'static str { - match *self { - Self::Installed => "installed", - Self::Downloading => "downloading", - Self::PackDownloading => "pack_downloading", - Self::NotInstalled => "not_installed", - } - } -} - -impl FromStr for ClusterStage { - fn from_str(s: &str) -> Result { - Ok(match s { - "installed" => Self::Installed, - "downloading" => Self::Downloading, - "pack_downloading" => Self::PackDownloading, - _ => Self::NotInstalled, - }) - } - - type Err = crate::Error; -} - -/// Relative Path wrapper to be used as an identifer for a cluster path. -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Hash)] -#[serde(transparent)] -pub struct ClusterPath(pub PathBuf); - -impl ClusterPath { - /// Get a new [`ClusterPath`] instance from an [`Into`]. - pub fn new(path: impl Into) -> Self { - Self(path.into()) - } - - pub async fn find_by_uuid(uuid: Uuid) -> crate::Result { - Ok(crate::cluster::get_by_uuid(uuid) - .await? - .ok_or_else(|| anyhow::anyhow!("Cluster does not exist"))? - .cluster_path()) - } - - /// Get the full [`PathBuf`] of the current cluster path. - pub async fn full_path(&self) -> crate::Result { - let state: std::sync::Arc> = State::get().await?; - let clusters_dir = state.directories.clusters_dir().await; - Ok(clusters_dir.join(&self.0)) - } - - /// Get the full [`PathBuf`] of the current cluster path. - pub async fn full_path_dirs(&self, dirs: &Directories) -> crate::Result { - let clusters_dir = dirs.clusters_dir().await; - Ok(clusters_dir.join(&self.0)) - } - - /// Validate the UTF of a cluster path. - pub fn validate(&self) -> crate::Result<&Self> { - self.0.to_str().ok_or_else(|| { - anyhow::anyhow!( - "invalid file path string {}!", - self.0.clone().to_string_lossy() - ) - })?; - Ok(self) - } - - /// Validate the cluster and clone the current [`ClusterPath`]. - pub async fn cluster_path(&self) -> crate::Result { - (crate::cluster::get(self).await?).map_or_else( - || { - Err(anyhow::anyhow!( - "failed to get path of unmanaged or corrupted cluster {}", - self.to_string() - ) - .into()) - }, - |c| Ok(c.cluster_path()), - ) - } - - /// Create a [`ClusterPath`] from a full [`PathBuf`]. - pub async fn from_path(path: PathBuf) -> crate::Result { - let path: PathBuf = io::canonicalize(path)?; - let clusters_dir = io::canonicalize(State::get().await?.directories.clusters_dir().await)?; - path.strip_prefix(clusters_dir) - .ok() - .and_then(|f| f.file_name()) - .ok_or_else(|| { - anyhow::anyhow!("path {} is not a cluster path", path.to_string_lossy()) - })?; - - Ok(Self(path)) - } -} - -impl std::fmt::Display for ClusterPath { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.display().fmt(f) - } -} - -/// Used for backwards compatibility for modpacks which handle windows paths strangely. -#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Hash)] -#[serde(into = "RawPackagePath", from = "RawPackagePath")] -pub struct InnerPathLinux(pub String); - -impl InnerPathLinux { - /// Get the first 2 components of the inner path. - #[must_use] - pub fn get_components(&self) -> String { - self.to_string() - .split('/') - .take(2) - .collect::>() - .join("/") - } -} - -impl std::fmt::Display for InnerPathLinux { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl From for InnerPathLinux { - fn from(value: RawPackagePath) -> Self { - Self(value.0.replace('\\', "/")) - } -} - -/// Used for backwards compatibility for modpacks which handle windows paths strangely. -#[derive(Serialize, Deserialize)] -#[serde(transparent)] -struct RawPackagePath(pub String); - -impl From for RawPackagePath { - fn from(value: InnerPathLinux) -> Self { - Self(value.0) - } -} - -// TODO: make it not a pathbuf, just get rid of all packagepath and clusterpath into str. - -/// Relative [`PathBuf`] for a specific [`Package`] of a [`Cluster`]. -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Hash)] -#[serde(transparent)] -pub struct PackagePath(PathBuf); - -impl PackagePath { - /// Transform a full [`PathBuf`] into a relative [`PackagePath`]. - pub async fn from_path(path: &PathBuf) -> crate::Result { - let clusters_dir: PathBuf = - std::fs::canonicalize(State::get().await?.directories.clusters_dir().await)?; - let path: PathBuf = std::fs::canonicalize(path)?; - let path = path - .strip_prefix(clusters_dir) - .ok() - .map(|p| p.components().skip(1).collect::()) - .ok_or_else(|| { - anyhow::anyhow!("path {path:?} does not exist in a cluster!", path = path) - })?; - - Ok(Self(path)) - } - - pub fn new(package_type: PackageType, file_name: String) -> crate::Result { - Ok(Self( - PathBuf::from_str(format!("{}/{}", package_type.get_folder(), file_name).as_str()) - .map_err(|e| anyhow::anyhow!("couldn't create package path from '{package_type}' '{file_name}': {e:?}"))? - )) - } - - /// Get the full [`PathBuf`] of the current package path. - pub async fn full_path(&self, cluster: &ClusterPath) -> crate::Result { - let cluster_dir = cluster.full_path().await?; - Ok(cluster_dir.join(&self.0)) - } - - pub fn package_type(&self) -> crate::Result { - PackageType::from_parent(&self.0).ok_or_else(|| anyhow::anyhow!("couldn't get package type of package path: {:?}", self.0).into()) - } - - pub fn file_name(&self) -> crate::Result { - self.0 - .file_name() - .map(|p| p.to_string_lossy().to_string()) - .ok_or_else(|| anyhow::anyhow!("couldn't get file name of package path: {:?}", self.0).into()) - } - - pub fn relative_path(&self) -> PathBuf { - self.0.clone() - } -} - -/// Represents a single Instance and installation of Minecraft -/// Contains settings and identifiers on a per-Cluster basis, falling back to default settings for Options<> -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct Cluster { - /// The Uuid of a specific cluster. - pub uuid: Uuid, - /// The download stage that of a cluster. - #[serde(default)] - pub stage: ClusterStage, - /// The core path of the cluster. - #[serde(default)] - pub path: PathBuf, - /// The associated cluster metadata. - pub meta: ClusterMeta, - /// The per-cluster JVM memory allocation options. - #[serde(skip_serializing_if = "Option::is_none")] - pub memory: Option, - /// The per-cluster JVM options. - #[serde(skip_serializing_if = "Option::is_none")] - pub java: Option, - /// The per-cluster Minecraft window default resolution. - #[serde(skip_serializing_if = "Option::is_none")] - pub resolution: Option, - /// The per-cluster Minecraft window fullscreen status. - pub force_fullscreen: Option, - // The per-cluster initialization hooks. - #[serde(skip_serializing_if = "Option::is_none")] - pub init_hooks: Option, -} - -/// Represents core Cluster metadata ([`Cluster#meta`]). -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct ClusterMeta { - /// The user-facing name of the cluster stored as a string. - pub name: String, - - /// The user-facing group of the cluster stored as a string. - #[serde(skip_serializing_if = "Option::is_none")] - pub group: Option, - /// The associated Minecraft version of the cluster as last updated. - pub mc_version: String, - - /// The associated mod [`Loader`] as specified in the cluster. - #[serde(default)] - pub loader: Loader, - /// The associated mod [`LoaderVersion`] if available. - #[serde(skip_serializing_if = "Option::is_none")] - pub loader_version: Option, - - /// The time that the cluster was created in [`DateTime`]. - #[serde(default)] - pub created_at: DateTime, - /// The time the cluster was last modified in [`DateTime`]. - #[serde(default)] - pub modified_at: DateTime, - /// The last time the cluster was played in [`DateTime`]. - /// (Defaults to None if the cluster has never been played) - #[serde(skip_serializing_if = "Option::is_none")] - pub played_at: Option>, - /// The overall time played in seconds stored as a [`u64`]. - #[serde(default)] - pub overall_played: u64, - /// The recent time played stored as a [`u64`]. - #[serde(default)] - pub recently_played: u64, - - /// The associated [`PackageData`] and modpack data for the cluster. - #[serde(skip_serializing_if = "Option::is_none")] - pub package_data: Option, - - /// The user-facing cluster icon as a [`PathBuf`]. - #[serde(skip_serializing_if = "Option::is_none")] - pub icon: Option, - /// The user-facing cluster icon as a URL string. - #[serde(skip_serializing_if = "Option::is_none")] - pub icon_url: Option, -} - -/// Optional data used to link a specific cluster to a package project. -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct PackageData { - /// The package ID as a String. - pub package_id: Option, - /// The version of the package as a String. - pub version_id: Option, - /// Whether or not the current package is locked (for legacy modpack support). - #[serde(default = "default_locked")] - pub locked: Option, -} - -#[must_use] -pub const fn default_locked() -> Option { - Some(true) -} - -/// Available mod loaders to be used for a cluster. -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Debug, Eq, PartialEq, Clone, Copy, Deserialize, Serialize, Default)] -#[serde(rename_all = "lowercase")] -pub enum Loader { - /// The default vanilla loader, no modding supported. - #[default] - Vanilla, - /// The `MinecraftForge` Minecraft mod loader. - Forge, - /// The `FabircMC` Minecraft mod loader. - Fabric, - /// The `NeoForge` Minecraft mod loader. - NeoForge, - /// The Quilt Minecraft mod loader. - Quilt, - /// The Legacy Fabric port mod loader. - LegacyFabric, -} - -impl Loader { - #[must_use] - pub const fn as_str(&self) -> &'static str { - match *self { - Self::Vanilla => "vanilla", - Self::Forge => "forge", - Self::Fabric => "fabric", - Self::Quilt => "quilt", - Self::NeoForge => "neoforge", - Self::LegacyFabric => "legacyfabric", - } - } - - #[must_use] - pub const fn as_meta_str(&self) -> &'static str { - match *self { - Self::Vanilla => "vanilla", - Self::Forge => "forge", - Self::Fabric => "fabric", - Self::Quilt => "quilt", - Self::NeoForge => "neo", - Self::LegacyFabric => "legacyfabric", - } - } - - #[must_use] - pub fn from_string(val: String) -> Self { - Self::from_str(val.as_str()) - } - - #[must_use] - pub fn from_str(val: &str) -> Self { - match val { - "forge" => Self::Forge, - "fabric" => Self::Fabric, - "quilt" => Self::Quilt, - "neoforge" => Self::NeoForge, - _ => Self::Vanilla, - } - } - - #[must_use] - pub const fn supports_mods(&self) -> bool { - !matches!(self, Self::Vanilla) - } -} - -impl TryFrom for Loader { - type Error = crate::ErrorKind; - - fn try_from(value: String) -> Result { - Ok(match value.to_lowercase().as_str() { - "vanilla" => Self::Vanilla, - "forge" => Self::Forge, - "fabric" => Self::Fabric, - "neoforge" => Self::NeoForge, - "quilt" => Self::Quilt, - "legacyfabric" | "legacy_fabric" => Self::LegacyFabric, - _ => return Err(anyhow::anyhow!("invalid loader type").into()), - }) - } -} - -impl std::fmt::Display for Loader { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match *self { - Self::Vanilla => "Vanilla", - Self::Forge => "Forge", - Self::Fabric => "Fabric", - Self::NeoForge => "NeoForge", - Self::Quilt => "Quilt", - Self::LegacyFabric => "LegacyFabric", - }) - } -} - -/// Custom Java arguments on a per-cluster basis. -#[derive(Serialize, Deserialize, Clone, Debug)] -#[cfg_attr(feature = "specta", derive(specta::Type))] -pub struct JavaOptions { - /// A custom java version from the global java store, if specified. - #[serde(skip_serializing_if = "Option::is_none")] - pub custom_version: Option, - /// Custom runtime arguments when running the cluster. - #[serde(skip_serializing_if = "Option::is_none")] - pub custom_arguments: Option>, - /// Custom environment variables when running the cluster. - #[serde(skip_serializing_if = "Option::is_none")] - pub custom_env_arguments: Option>, -} - -impl Cluster { - /// Create a new Cluster - #[tracing::instrument] - pub async fn new(uuid: Uuid, name: String, version: String) -> crate::Result { - if name.trim().is_empty() { - return Err(anyhow::anyhow!("invalid instance name (empty)").into()); - } - - Ok(Self { - uuid, - stage: ClusterStage::NotInstalled, - path: PathBuf::new().join(&name), - meta: ClusterMeta { - name, - group: None, - mc_version: version, - loader: Loader::Vanilla, - loader_version: None, - created_at: Utc::now(), - modified_at: Utc::now(), - package_data: None, - played_at: None, - overall_played: 0, - recently_played: 0, - icon: None, - icon_url: None, - }, - java: None, - memory: None, - resolution: None, - force_fullscreen: None, - init_hooks: None, - }) - } - - /// Get the [`ClusterPath`] of the specified [`Cluster`]. - #[inline] - #[must_use] - pub fn cluster_path(&self) -> ClusterPath { - ClusterPath::new(&self.path) - } - - /// Set the icon [`bytes::Bytes`] for this cluster. - #[tracing::instrument(skip(self, io_semaphore, icon))] - pub async fn set_icon_path<'a>( - &'a mut self, - cache_path: &Path, - io_semaphore: &IoSemaphore, - icon: &Path, - file_name: &str, - ) -> crate::Result<()> { - let icon = io::read(icon).await?; - self.set_icon_bytes(cache_path, io_semaphore, icon.into(), file_name) - .await?; - Ok(()) - } - - /// Set the icon [`bytes::Bytes`] for this cluster. - #[tracing::instrument(skip(self, io_semaphore, icon))] - pub async fn set_icon_bytes<'a>( - &'a mut self, - cache_path: &Path, - io_semaphore: &IoSemaphore, - icon: bytes::Bytes, - file_name: &str, - ) -> crate::Result<()> { - let file = write_icon(file_name, cache_path, icon, io_semaphore).await?; - self.meta.icon = Some(file); - self.meta.modified_at = Utc::now(); - Ok(()) - } - - /// Handle a cluster crash. - pub fn handle_crash(path: ClusterPath) { - tokio::task::spawn(async move { - let res = async { - let cluster = crate::api::cluster::get(&path).await?; - if let Some(cluster) = cluster { - if cluster.stage == ClusterStage::Installed { - send_message(&format!( - "cluster {} has crashed! visit the logs page for a crash report.", - cluster.meta.name - )) - .await?; - } - } - - Ok::<(), crate::Error>(()) - } - .await; - - match res { - Ok(()) => {} - Err(err) => tracing::warn!("failed to send crash report to frontend {err}"), - }; - }); - } - - /// get the full path to the current cluster. - pub async fn get_full_path(&self) -> crate::Result { - let state = State::get().await?; - let clusters_dir = state.directories.clusters_dir().await; - Ok(clusters_dir.join(&self.path)) - } - - /// get full paths and subpaths - pub async fn get_full_subs(&self) -> crate::Result> { - let mut files = Vec::new(); - let cluster_path = self.get_full_path().await?; - let mut paths = |path: &str| { - let new = cluster_path.join(path); - if new.exists() { - for sub in std::fs::read_dir(&new).map_err(|e| IOError::with_path(e, &new))? { - let sub = sub.map_err(IOError::from)?.path(); - if sub.is_file() { - files.push(sub); - } - } - } - Ok::<(), crate::Error>(()) - }; - - paths(PackageType::Mod.get_folder())?; - paths(PackageType::ShaderPack.get_folder())?; - paths(PackageType::ResourcePack.get_folder())?; - paths(PackageType::DataPack.get_folder())?; - - Ok(files) - } - - /// watch the filesystem for changes with [`notify`]. - #[tracing::instrument(skip(watcher))] - #[onelauncher_macros::memory] - pub async fn watch( - cluster_path: &Path, - watcher: &mut Debouncer, - ) -> crate::Result<()> { - async fn watch_path( - cluster_path: &Path, - watcher: &mut Debouncer, - path: &str, - ) -> crate::Result<()> { - let path = cluster_path.join(path); - io::create_dir_all(&path).await?; - watcher - .watcher() - .watch(&cluster_path.join(path), notify::RecursiveMode::Recursive)?; - Ok(()) - } - - watch_path(cluster_path, watcher, PackageType::Mod.get_folder()).await?; - watch_path(cluster_path, watcher, PackageType::ShaderPack.get_folder()).await?; - watch_path( - cluster_path, - watcher, - PackageType::ResourcePack.get_folder(), - ) - .await?; - watch_path(cluster_path, watcher, PackageType::DataPack.get_folder()).await?; - watch_path(cluster_path, watcher, "crash-reports").await?; - - Ok(()) - } -} - -impl Clusters { - /// Initialize the cluster manager and HashMap. - #[tracing::instrument(skip(watcher))] - #[onelauncher_macros::memory] - pub async fn initialize( - dirs: &Directories, - watcher: &mut Debouncer, - ) -> crate::Result { - let mut clusters = HashMap::new(); - let clusters_dir = dirs.clusters_dir().await; - io::create_dir_all(&&clusters_dir).await?; - watcher - .watcher() - .watch(&clusters_dir, notify::RecursiveMode::NonRecursive)?; - let mut files = io::read_dir(&dirs.clusters_dir().await).await?; - - while let Some(file) = files.next_entry().await.map_err(IOError::from)? { - let path = file.path(); - if path.is_dir() { - let cluster = match Self::from_dir(&path, dirs).await { - Ok(cluster) => Some(cluster), - Err(err) => { - tracing::warn!("failed to load cluster {err}. skipping"); - None - } - }; - - if let Some(cluster_) = cluster { - let path = io::canonicalize(path)?; - Cluster::watch(&path, watcher).await?; - clusters.insert(cluster_.cluster_path(), cluster_); - } - } - } - - Ok(Self(clusters)) - } - - /// update registered packages - #[tracing::instrument] - #[onelauncher_macros::memory] - pub async fn update_packages() { - // TODO: Pacakges - } - - /// update all available package versions - #[tracing::instrument] - #[onelauncher_macros::memory] - pub async fn update_versions() { - let res = async { - let state = State::get().await?; - let mut updateable: Vec<(ClusterPath, String)> = Vec::new(); - - { - let clusters = state.clusters.read().await; - for (cluster_path, cluster) in &clusters.0 { - if let Some(package_data) = &cluster.meta.package_data { - if let Some(linked_package) = &package_data.package_id { - updateable.push((cluster_path.clone(), linked_package.clone())); - } - } - } - } - - // TODO - - { - let clusters = state.clusters.read().await; - clusters.sync().await?; - } - - Ok::<(), crate::Error>(()) - } - .await; - - match res { - Ok(()) => {} - Err(err) => tracing::warn!("failed to update managed packages: {err}"), - }; - } - - /// insert a cluster into the HashMap - #[tracing::instrument(skip(self, cluster))] - #[onelauncher_macros::memory] - pub async fn insert(&mut self, cluster: Cluster, dont_watch: bool) -> crate::Result<&Self> { - send_cluster( - cluster.uuid, - &cluster.cluster_path(), - &cluster.meta.name, - ClusterPayloadType::Inserted, - ) - .await?; - - if !dont_watch { - let state = State::get().await?; - let mut watcher = state.watcher.write().await; - Cluster::watch(&cluster.get_full_path().await?, &mut watcher).await?; - } - - let cluster_name = cluster.cluster_path(); - cluster_name.validate()?; - self.0.insert(cluster_name, cluster); - Ok(self) - } - - /// remove a cluster from the HashMap - #[tracing::instrument(skip(self))] - pub async fn remove(&mut self, cluster_path: &ClusterPath) -> crate::Result> { - let cluster = self.0.remove(cluster_path); - let path = cluster_path.full_path().await?; - if path.exists() { - io::remove_dir_all(&path).await?; - } - - Ok(cluster) - } - - /// sync all available clusters - #[tracing::instrument(skip_all)] - pub async fn sync(&self) -> crate::Result<&Self> { - let _state = State::get().await?; - stream::iter(self.0.iter()) - .map(Ok::<_, crate::Error>) - .try_for_each_concurrent(None, |(_, cluster)| async move { - let json = serde_json::to_vec(&cluster)?; - let json_path = cluster.get_full_path().await?.join(CLUSTER_FILE); - io::write(&json_path, &json).await?; - Ok::<_, crate::Error>(()) - }) - .await?; - - Ok(self) - } - - /// read a cluster from a directory - async fn from_dir(path: &Path, dirs: &Directories) -> crate::Result { - let json = io::read(&path.join(CLUSTER_FILE)).await?; - let mut cluster = serde_json::from_slice::(&json)?; - cluster.path = PathBuf::from( - path.strip_prefix(dirs.clusters_dir().await) - .map_err(|err| anyhow::anyhow!("failed to strip prefix {err}"))?, - ); - Ok(cluster) - } - - /// sync a cluster - pub fn sync_cluster(cluster_path: ClusterPath) { - tokio::task::spawn(async move { - let span = tracing::span!(tracing::Level::INFO, "sync_cluster"); - let res = span.in_scope(|| async { - let state = State::get().await?; - let dirs = &state.directories; - let mut clusters = state.clusters.write().await; - - if let Some(cluster) = clusters.0.get_mut(&cluster_path) { - if !cluster.get_full_path().await?.exists() { - send_cluster( - cluster.uuid, - &cluster_path, - &cluster.meta.name, - ClusterPayloadType::Deleted, - ) - .await?; - tracing::debug!("removed non-existant fs cluster!"); - clusters.0.remove(&cluster_path); - } - } else if cluster_path.full_path().await?.exists() { - clusters - .insert( - Self::from_dir(&cluster_path.full_path().await?, dirs).await?, - false, - ) - .await?; - - tokio::task::spawn(async move { - // Cluster::sync_packages(&cluster_path).await; - }); - } - Ok::<(), crate::Error>(()) - }) - .await; - - match res { - Ok(()) => {} - Err(err) => tracing::warn!("failed to fetch a cluster: {err}"), - }; - }); - } -} diff --git a/packages/core_old/src/store/credentials.rs b/packages/core_old/src/store/credentials.rs deleted file mode 100644 index 220733fd..00000000 --- a/packages/core_old/src/store/credentials.rs +++ /dev/null @@ -1,80 +0,0 @@ -//! Core credential management and storage with IOTA Stronghold. - -use super::Directories; -use crate::utils::http::IoSemaphore; -use iota_stronghold::{KeyProvider, SnapshotPath, Stronghold}; -use serde::{Serialize, Serializer}; -use std::ops::Deref; -use std::path::Path; -use zeroize::Zeroizing; - -/// A K/V encrypted store to handle digital secrets using IOTA Stronghold. -#[derive(Debug)] -pub struct Credentials { - inner: Stronghold, - path: SnapshotPath, - keyprovider: KeyProvider, -} - -impl Credentials { - /// Initialize the K/V encrypted stronghold. - pub fn new>( - path: P, - password: Zeroizing>, - _dirs: &Directories, - _io_semaphore: &IoSemaphore, - ) -> crate::Result { - let path = SnapshotPath::from_path(path); - let stronghold = Stronghold::default(); - let keyprovider = KeyProvider::try_from(password).map_err(StrongholdError::MemoryError)?; - if path.exists() { - stronghold - .load_snapshot(&keyprovider, &path) - .map_err(StrongholdError::ClientError)?; - } - Ok(Self { - inner: stronghold, - path, - keyprovider, - }) - } - - pub fn save(&self) -> crate::Result<()> { - self.inner - .commit_with_keyprovider(&self.path, &self.keyprovider) - .map_err(StrongholdError::ClientError)?; - Ok(()) - } - - pub const fn inner(&self) -> &Stronghold { - &self.inner - } -} - -impl Deref for Credentials { - type Target = Stronghold; - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -#[derive(thiserror::Error, Debug)] -pub enum StrongholdError { - #[error("stronghold not initialized")] - StrongholdNotInitialized, - #[error(transparent)] - ClientError(#[from] iota_stronghold::ClientError), - #[error(transparent)] - MemoryError(#[from] iota_stronghold::MemoryError), - #[error(transparent)] - ProcedureError(#[from] iota_stronghold::procedures::ProcedureError), -} - -impl Serialize for StrongholdError { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(self.to_string().as_str()) - } -} diff --git a/packages/core_old/src/store/dirs.rs b/packages/core_old/src/store/dirs.rs deleted file mode 100644 index 8eab75de..00000000 --- a/packages/core_old/src/store/dirs.rs +++ /dev/null @@ -1,191 +0,0 @@ -//! Handles core directory management -use std::path::PathBuf; -use tokio::sync::RwLock; - -use super::Settings; -use crate::constants; - -/// Directory management and utilities. -#[derive(Debug)] -pub struct Directories { - /// Settings: All small caches and the core settings.json - pub settings_dir: PathBuf, - /// Base Configuration: Clusters and all Assets (should be changable as a setting that can go across file systems) - /// By default this should be the same as the `settings_dir` - pub config_dir: RwLock, - /// The current working directory of the directory manager. - pub cwd: PathBuf, -} - -impl Directories { - /// Bootstrap the initial settings directory on first startup. - #[must_use] - pub fn init_settings_dir() -> Option { - Self::env_path("ONELAUNCHER_CONFIG") - .or_else(|| Some(dirs::data_dir()?.join(constants::NAME))) - } - - /// Bootstrap the initial settings directory on first startup. - #[inline] - pub fn init_settings_file() -> crate::Result { - let settings_dir = Self::init_settings_dir().ok_or(DirectoryError::ConfigDir)?; - Ok(settings_dir.join("settings.json")) - } - - /// Initialize the core directory manager - #[tracing::instrument] - pub fn initalize(settings: &Settings) -> crate::Result { - let cwd = std::env::current_dir().map_err(DirectoryError::WorkingDir)?; - let settings_dir = Self::init_settings_dir().ok_or(DirectoryError::ConfigDir)?; - let config_dir = settings - .config_dir - .clone() - .ok_or(DirectoryError::ConfigDir)?; - Ok(Self { - settings_dir, - config_dir: RwLock::new(config_dir), - cwd, - }) - } - - /// Get the `config_dir/metadata` folder within the core config directory. - #[inline] - pub async fn metadata_dir(&self) -> PathBuf { - self.config_dir - .read() - .await - .join(constants::METADATA_FOLDER) - } - - /// Get the `config_dir/metadata/libraries` folder for Minecraft libraries. - #[inline] - pub async fn libraries_dir(&self) -> PathBuf { - self.metadata_dir().await.join("libraries") - } - - /// Get the `config_dir/metadata/java` directory. - #[inline] - pub async fn java_dir(&self) -> PathBuf { - self.metadata_dir().await.join("java_versions") - } - - /// Get the `config_dir/metadata/caches` directory. - #[inline] - pub async fn caches_dir(&self) -> PathBuf { - self.metadata_dir().await.join("caches") - } - - /// Bootstrap the core logs directory and get it as a [`PathBuf`]. - #[inline] - #[must_use] - pub fn logs_dir() -> Option { - Self::init_settings_dir().map(|d| d.join("logs")) - } - - /// Get the `settings_dir/settings.json` file as a [`PathBuf`]. - #[inline] - pub fn settings_file(&self) -> PathBuf { - self.settings_dir.join(constants::SETTINGS_FILE) - } - - /// Get a [`PathBuf`] from a provided environment variable. - #[inline] - fn env_path(name: &str) -> Option { - std::env::var_os(name).map(PathBuf::from) - } - - /// Get the `config_dir/metadata/assets` directory. - #[inline] - pub async fn assets_dir(&self) -> PathBuf { - self.metadata_dir().await.join("assets") - } - - /// Get the `config_dir/metadata/assets/indexes` directory. - #[inline] - pub async fn index_dir(&self) -> PathBuf { - self.assets_dir().await.join("indexes") - } - - /// Get the `config_dir/metadata/assets/objects` directory. - #[inline] - pub async fn objects_dir(&self) -> PathBuf { - self.assets_dir().await.join("objects") - } - - /// Get the `config_dir/metadata/assets/objects/{hash}` directory. - #[inline] - pub async fn object_dir(&self, hash: &str) -> PathBuf { - self.objects_dir().await.join(&hash[..2]).join(hash) - } - - /// Get the `config_dir/clusters` directory. - #[inline] - pub async fn clusters_dir(&self) -> PathBuf { - self.config_dir.read().await.join("clusters") - } - - /// Get the `config_dir/clusters/{uuid}` directory. - #[inline] - pub async fn cluster_dir(&self, uuid: uuid::Uuid) -> PathBuf { - self.clusters_dir().await.join(uuid.to_string()) - } - - /// Get the `{cluster_path}/logs` directory. - #[inline] - pub async fn cluster_logs_dir(cluster_path: &super::ClusterPath) -> crate::Result { - Ok(cluster_path.full_path().await?.join("logs")) - } - - /// Get the `{cluster_path}/crash_reports` directory. - #[inline] - pub async fn crash_reports_dir(cluster_path: &super::ClusterPath) -> crate::Result { - Ok(cluster_path.full_path().await?.join("crash-reports")) - } - - /// Get the `config_dir/metadata/resources` directory. - #[inline] - pub async fn legacy_assets_dir(&self) -> PathBuf { - self.metadata_dir().await.join("resources") - } - - /// Get the `config_dir/metadata/natives` directory. - #[inline] - pub async fn natives_dir(&self) -> PathBuf { - self.metadata_dir().await.join("natives") - } - - /// Get the `config_dir/metadata/natives/{version}` directory. - #[inline] - pub async fn version_natives_dir(&self, version: &str) -> PathBuf { - self.natives_dir().await.join(version) - } - - /// Get the Minecraft `config_dir/metadata/versions` directory. - #[inline] - pub async fn versions_dir(&self) -> PathBuf { - self.metadata_dir().await.join("versions") - } - - /// Get the Minecraft `config_dir/metadata/versions/{version}` directory. - #[inline] - pub async fn version_dir(&self, version: &str) -> PathBuf { - self.versions_dir().await.join(version) - } - - /// Get the `config_dir/icons` directory. - #[inline] - pub async fn icons_dir(&self) -> PathBuf { - self.config_dir.read().await.join("icons") - } -} - -/// Represents a core directory management error. -#[derive(Debug, thiserror::Error)] -pub enum DirectoryError { - /// An error for when a config directory cannot be initialized. - #[error("failed to get the user's config directory")] - ConfigDir, - /// A wrapper over [`std::io::Error`] for cwd fetching errors. - #[error("could not open working directory {0}")] - WorkingDir(std::io::Error), -} diff --git a/packages/core_old/src/store/discord.rs b/packages/core_old/src/store/discord.rs deleted file mode 100644 index 3b469701..00000000 --- a/packages/core_old/src/store/discord.rs +++ /dev/null @@ -1,162 +0,0 @@ -//! Handles Discord rich prescence integration -use std::sync::atomic::AtomicBool; -use std::sync::Arc; - -use discord_rich_presence::activity::{Activity, Assets, Button}; -use discord_rich_presence::{DiscordIpc, DiscordIpcClient}; -use tokio::sync::RwLock; - -use crate::constants::DISCORD_RPC_CLIENT_ID; -use crate::State; - -/// A structure used to manage a Discord IPC client. -pub struct DiscordRPC { - /// The managed [`DiscordIpcClient`] used for this IPC client. - client: Arc>, - /// Whether or not we are connected to the IPC server and internet. - connected: Arc, -} - -impl DiscordRPC { - /// Initialize the Discord IPC client and attempt to connect to the server. - /// If an error is returned, it will be ignored since Discord RPC isnt essential for the launcher to load. - pub fn initialize(is_offline: bool) -> crate::Result { - let mut discord_ipc = DiscordIpcClient::new(DISCORD_RPC_CLIENT_ID) - .map_err(|e| anyhow::anyhow!("failed to create discord client {}", e))?; - - let connected = if is_offline { - Arc::new(AtomicBool::new(false)) - } else { - let result = discord_ipc.connect(); - if result.is_ok() { - Arc::new(AtomicBool::new(true)) - } else { - Arc::new(AtomicBool::new(false)) - } - }; - - let client = Arc::new(RwLock::new(discord_ipc)); - Ok(Self { client, connected }) - } - - /// Set the Discord IPC activity with a message and try to reconnect if the initial connection fails. - pub async fn set_activity(&self, msg: &str, reconnect_if_fail: bool) -> crate::Result<()> { - if !self.retry_online().await { - return Ok(()); - } - let state = State::get().await?; - let settings = state.settings.read().await; - if settings.disable_discord { - Ok(self.clear_activity(true).await?) - } else { - Ok(self.apply_activity(msg, reconnect_if_fail).await?) - } - } - - /// Do not use this function, use [`DiscordRPC#set_activity`] - pub async fn apply_activity(&self, msg: &str, reconnect_if_fail: bool) -> crate::Result<()> { - if !self.retry_connection().await { - return Ok(()); - } - - let activity = Activity::new() - .state(msg) - .buttons(vec![Button::new("Download", "https://polyfrost.org/")]) - .assets( - Assets::new() - .large_image("onelauncher_logo_512") - .large_text("OneLauncher"), - ); - - let mut client: tokio::sync::RwLockWriteGuard<'_, DiscordIpcClient> = - self.client.write().await; - let result = client.set_activity(activity.clone()); - let could_not_set = |e: Box| { - anyhow::anyhow!("failed to apply discord activity {}", e) - }; - - if reconnect_if_fail { - if let Err(_e) = result { - client - .reconnect() - .map_err(|e| anyhow::anyhow!("failed to reconnect to Discord RPC {}", e))?; - - return Ok(client.set_activity(activity).map_err(could_not_set)?); - } - } else { - result.map_err(could_not_set)?; - } - - Ok(()) - } - - /// Attempt to check if we are still in an offline environment. - pub async fn retry_online(&self) -> bool { - let Ok(state) = State::get().await else { - return false; - }; - let offline = state.offline.read().await; - if *offline { - return false; - } - true - } - - /// Retry the Discord IPC connection. - pub async fn retry_connection(&self) -> bool { - let mut client = self.client.write().await; - if !self.connected.load(std::sync::atomic::Ordering::Relaxed) { - if client.connect().is_ok() { - self.connected - .store(true, std::sync::atomic::Ordering::Relaxed); - return true; - } - return false; - } - true - } - - /// Clear the current Discord IPC activity, reconnecting if requested. - pub async fn clear_activity(&self, reconnect_if_fail: bool) -> crate::Result<()> { - if !self.retry_online().await || !self.retry_connection().await { - return Ok(()); - } - - let mut client = self.client.write().await; - let result = client.clear_activity(); - let could_not_clear = |e: Box| { - anyhow::anyhow!("failed to clear discord activity {}", e) - }; - - if reconnect_if_fail { - if result.is_err() { - client - .reconnect() - .map_err(|e| anyhow::anyhow!("failed to reconnect to Discord RPC {}", e))?; - - return Ok(client.clear_activity().map_err(could_not_clear)?); - } - } else { - result.map_err(could_not_clear)?; - } - - Ok(()) - } - - /// Clear and disable the Discord IPC, reconnecting if requested. - pub async fn clear(&self, reconnect_if_fail: bool) -> crate::Result<()> { - let state: Arc> = State::get().await?; - - { - let settings = state.settings.read().await; - if settings.disable_discord { - println!("discord is disabled.. clearing"); - return self.clear_activity(true).await; - } - } - - self.set_activity("Idling...", reconnect_if_fail).await?; - - Ok(()) - } -} diff --git a/packages/core_old/src/store/java_versions.rs b/packages/core_old/src/store/java_versions.rs deleted file mode 100644 index 2261bd0a..00000000 --- a/packages/core_old/src/store/java_versions.rs +++ /dev/null @@ -1,76 +0,0 @@ -//! Handles all available Java installations - -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::path::PathBuf; - -use crate::utils::java; -use crate::utils::java::JavaVersion; - -/// A `HashMap` of all located and installed available Java versions -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct JavaVersions(HashMap); - -impl Default for JavaVersions { - fn default() -> Self { - Self::new() - } -} - -impl JavaVersions { - /// Create an empty `JavaVersions` `HashMap`. - #[must_use] - pub fn new() -> Self { - Self(HashMap::new()) - } - - /// Inserts a key-value pair into the map. - pub fn insert(&mut self, key: String, java: JavaVersion) { - self.0.insert(key, java); - } - - /// Removes a key from the map, returning the value at the key if the key was previously in the map. - pub fn remove(&mut self, key: &String) { - self.0.remove(key); - } - - /// Returns a reference to the value corresponding to the key. - #[must_use] - pub fn get(&self, key: &String) -> Option<&JavaVersion> { - self.0.get(key) - } - - /// Returns a mutable reference to the value corresponding to the key. - pub fn get_mut(&mut self, key: &String) -> Option<&mut JavaVersion> { - self.0.get_mut(key) - } - - /// Returns the number of elements in the map. - #[must_use] - pub fn count(&self) -> usize { - self.0.len() - } - - /// A collection visiting all keys in arbitrary order. - #[must_use] - pub fn keys(&self) -> Vec { - self.0.keys().cloned().collect() - } - - /// Validates all stored java versions. - pub async fn validate(&self) -> bool { - for java in self.0.values() { - let runtime = java::check_java_instance(PathBuf::from(&java.path).as_path()).await; - if let Some(runtime) = runtime { - if runtime.version != java.version { - return false; - } - } else { - return false; - } - } - - true - } -} diff --git a/packages/core_old/src/store/metadata.rs b/packages/core_old/src/store/metadata.rs deleted file mode 100644 index f1fa8a9b..00000000 --- a/packages/core_old/src/store/metadata.rs +++ /dev/null @@ -1,263 +0,0 @@ -//! Handles fetching metadata via interpulse. - -use interpulse::api::minecraft::VersionManifest as MinecraftManifest; -use interpulse::api::modded::Manifest as ModdedManifest; -use serde::{Deserialize, Serialize}; - -use crate::constants::METADATA_API_URL; -use crate::utils::http::{fetch, read_json, write, FetchSemaphore, IoSemaphore}; -use crate::State; - -use super::Directories; - -/// A structure of manifests and metadata fetching utilities. -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct Metadata { - /// The [`MinecraftManifest`] associated with core Minecraft versions. - pub minecraft: Option, - /// The [`ModdedManifest`] associated with core Fabric versions. - pub fabric: Option, - /// The [`ModdedManifest`] associated with core Quilt versions. - pub quilt: Option, - /// The [`ModdedManifest`] associated with core `NeoForge` versions. - pub neoforge: Option, - /// The [`ModdedManifest`] associated with core Forge versions. - pub forge: Option, - // /// The [`ModdedManifest`] associated with core Legacy Fabric versions. - // pub legacy_fabric: Option, -} - -impl Metadata { - /// Get the formatted manifest for a specific [`Metadata`] type. - fn get_manifest(name: &str, version: usize) -> String { - format!("{METADATA_API_URL}/{name}/v{version}/manifest.json") - } - - /// Fetch all available metadata types that are currently [`None`] and get a new [`Metadata`] structure. - /// Useful for when reading from cache and a certain object not existing - pub async fn fetch_errored(&mut self, semaphore: &FetchSemaphore) -> crate::Result { - let mut did_change = false; - if self.minecraft.is_none() { - self.minecraft = Self::fetch_minecraft(semaphore).await; - - if self.minecraft.is_some() { - did_change = true; - } - } - - if self.fabric.is_none() { - self.fabric = Self::fetch_fabric(semaphore).await; - - if self.fabric.is_some() { - did_change = true; - } - } - - if self.quilt.is_none() { - self.quilt = Self::fetch_quilt(semaphore).await; - - if self.quilt.is_some() { - did_change = true; - } - } - - if self.neoforge.is_none() { - self.neoforge = Self::fetch_neoforge(semaphore).await; - - if self.neoforge.is_some() { - did_change = true; - } - } - - if self.forge.is_none() { - self.forge = Self::fetch_forge(semaphore).await; - - if self.forge.is_some() { - did_change = true; - } - } - - Ok(did_change) - } - - /// Fetch all available metadata types and get a new [`Metadata`] structure. - pub async fn fetch(semaphore: &FetchSemaphore) -> crate::Result { - let (minecraft, fabric, quilt, neoforge, forge) = tokio::join! { - Self::fetch_minecraft(semaphore), - Self::fetch_fabric(semaphore), - Self::fetch_quilt(semaphore), - Self::fetch_neoforge(semaphore), - Self::fetch_forge(semaphore) - }; - - Ok(Self { - minecraft, - fabric, - quilt, - neoforge, - forge, - }) - } - - async fn fetch_minecraft(semaphore: &FetchSemaphore) -> Option { - let url = Self::get_manifest( - "minecraft", - interpulse::api::minecraft::CURRENT_FORMAT_VERSION, - ); - fetch_version_manifest(Some(&url), semaphore).await.ok() - } - - async fn fetch_fabric(semaphore: &FetchSemaphore) -> Option { - let url = Self::get_manifest( - "fabric", - interpulse::api::modded::CURRENT_FABRIC_FORMAT_VERSION, - ); - fetch_modded_manifest(&url, semaphore).await.ok() - } - - async fn fetch_quilt(semaphore: &FetchSemaphore) -> Option { - let url = Self::get_manifest( - "quilt", - interpulse::api::modded::CURRENT_QUILT_FORMAT_VERSION, - ); - fetch_modded_manifest(&url, semaphore).await.ok() - } - - async fn fetch_neoforge(semaphore: &FetchSemaphore) -> Option { - let url = Self::get_manifest( - "neoforge", - interpulse::api::modded::CURRENT_NEOFORGE_FORMAT_VERSION, - ); - fetch_modded_manifest(&url, semaphore).await.ok() - } - - async fn fetch_forge(semaphore: &FetchSemaphore) -> Option { - let url = Self::get_manifest( - "forge", - interpulse::api::modded::CURRENT_FORGE_FORMAT_VERSION, - ); - fetch_modded_manifest(&url, semaphore).await.ok() - } - - /// Initialize the core Metadata manager. - #[tracing::instrument(skip(io_semaphore, fetch_semaphore))] - #[onelauncher_macros::memory] - pub async fn initialize( - dirs: &Directories, - online: bool, - io_semaphore: &IoSemaphore, - fetch_semaphore: &FetchSemaphore, - ) -> crate::Result { - let mut metadata = None; - let path = dirs.caches_dir().await.join("metadata.json"); - let backup = dirs.caches_dir().await.join("metadata.json.bak"); - let mut should_write = false; - - if let Ok(mut metadata_json) = read_json::(&path, io_semaphore).await { - should_write = metadata_json.fetch_errored(fetch_semaphore).await?; - - metadata = Some(metadata_json); - } else if online { - let res = async { - let fetch_data = Self::fetch(fetch_semaphore).await?; - should_write = true; - - metadata = Some(fetch_data); - Ok::<(), crate::Error>(()) - } - .await; - - match res { - Ok(()) => {} - Err(err) => { - tracing::warn!("failed to fetch metadata: {err}"); - } - } - } else if let Ok(metadata_json) = read_json::(&backup, io_semaphore).await { - metadata = Some(metadata_json); - onelauncher_utils::io::copy(&backup, &path).await?; - } - - if should_write { - write( - &path, - &serde_json::to_vec(&metadata).unwrap_or_default(), - io_semaphore, - ) - .await?; - write( - &backup, - &serde_json::to_vec(&metadata).unwrap_or_default(), - io_semaphore, - ) - .await?; - } - - metadata.map_or_else( - || Err(anyhow::anyhow!("failed to fetch launcher metadata").into()), - Ok, - ) - } - - /// Update and backup all available metadata. - pub async fn update() { - let res = async { - let state = State::get().await?; - let fetch_data = Self::fetch(&state.fetch_semaphore).await?; - - let path = state.directories.caches_dir().await.join("metadata.json"); - let backup = state - .directories - .caches_dir() - .await - .join("metadata.json.bak"); - - if path.exists() { - onelauncher_utils::io::copy(&path, &backup).await?; - } - - write( - &path, - &serde_json::to_vec(&fetch_data)?, - &state.io_semaphore, - ) - .await?; - - let mut old_metadata = state.metadata.write().await; - *old_metadata = fetch_data; - - Ok::<(), crate::Error>(()) - } - .await; - - match res { - Ok(()) => {} - Err(err) => { - tracing::warn!("failed to update launcher metadata: {err}"); - } - }; - } -} - -async fn fetch_version_manifest( - url: Option<&str>, - semaphore: &FetchSemaphore, -) -> crate::Result { - let url = url.unwrap_or(interpulse::api::minecraft::VERSION_MANIFEST_URL); - Ok(serde_json::from_slice( - &fetch(url, None, semaphore).await.inspect_err(|_| { - tracing::error!("couldn't fetch version manifest at '{}'", url); - })?, - )?) -} - -async fn fetch_modded_manifest( - url: &str, - semaphore: &FetchSemaphore, -) -> crate::Result { - Ok(serde_json::from_slice( - &fetch(url, None, semaphore).await.inspect_err(|_| { - tracing::error!("couldn't fetch modded manifest at '{}'", url); - })?, - )?) -} diff --git a/packages/core_old/src/store/minecraft.rs b/packages/core_old/src/store/minecraft.rs deleted file mode 100644 index 6d06ace9..00000000 --- a/packages/core_old/src/store/minecraft.rs +++ /dev/null @@ -1,1043 +0,0 @@ -//! Handles MSA Authentication flow. - -use base64::Engine; -use chrono::{DateTime, Utc}; -use p256::ecdsa::signature::Signer; -use p256::ecdsa::SigningKey; -use p256::pkcs8::{DecodePrivateKey, EncodePrivateKey}; -use rand::Rng; -use serde::{Deserialize, Serialize}; -use serde_json::json; -use sha2::Digest; -use std::collections::HashMap; -use std::fmt::Write; -use uuid::Uuid; - -use crate::constants::{AUTH_FILE, MINECRAFT_CLIENT_ID, MINECRAFT_REDIRECT_URL, MINECRAFT_SCOPES}; - -/// The core state of Microsoft authentication for the launcher -#[derive(Serialize, Deserialize, Debug)] -pub struct MinecraftState { - /// A [`HashMap`] of all logged in users by [`Uuid`] and their Minecraft credentials. - pub users: HashMap, - /// The current stored device token to be used while logging in. - pub token: Option, - /// The default Minecraft user which the launcher uses, by [`Uuid`]. - pub default_user: Option, -} - -impl MinecraftState { - /// Initialize a new Minecraft global state. - #[tracing::instrument] - pub async fn initialize( - dirs: &super::Directories, - io_semaphore: &crate::utils::http::IoSemaphore, - ) -> crate::Result { - let path = dirs.caches_dir().await.join(AUTH_FILE); - let store = crate::utils::http::read_json(&path, io_semaphore) - .await - .ok(); - - store.map_or_else( - || { - Ok(Self { - users: HashMap::new(), - token: None, - default_user: None, - }) - }, - Ok, - ) - } - - /// Save the current Minecraft credentials. - #[tracing::instrument(skip(self))] - pub async fn save(&self) -> crate::Result<()> { - let state = crate::State::get().await?; - let path = state.directories.caches_dir().await.join(AUTH_FILE); - crate::utils::http::write(&path, &serde_json::to_vec(&self)?, &state.io_semaphore).await?; - Ok(()) - } - - /// Refresh all tokens in the global state. - #[tracing::instrument(skip(self))] - async fn refresh( - &mut self, - current_date: DateTime, - force: bool, - ) -> crate::Result<(DeviceKey, DeviceToken, DateTime, bool)> { - macro_rules! device_key { - ($self:ident, $device_key:expr, $device_token:expr, $SaveDeviceToken:path) => {{ - let key = device_key()?; - let res = device_token(&key, current_date).await?; - - self.token = Some(SaveDeviceToken { - id: key.id.clone(), - private_key: key - .key - .to_pkcs8_pem(p256::pkcs8::LineEnding::default()) - .map_err(|err| MinecraftAuthError::PKCS8Error(err))? - .to_string(), - x: key.x.clone(), - y: key.y.clone(), - token: res.value.clone(), - }); - self.save().await?; - - (key, res.value, res.date, true) - }}; - } - - let (key, token, date, valid_date) = if let Some(ref token) = self.token { - if let Ok(private_key) = SigningKey::from_pkcs8_pem(&token.private_key) { - if token.token.not_after > Utc::now() && !force { - ( - DeviceKey { - id: token.id.clone(), - key: private_key, - x: token.x.clone(), - y: token.y.clone(), - }, - token.token.clone(), - current_date, - false, - ) - } else { - let key = DeviceKey { - id: token.id.clone(), - key: private_key, - x: token.x.clone(), - y: token.y.clone(), - }; - - let res = device_token(&key, current_date).await?; - - (key, res.value, res.date, true) - } - } else { - device_key!(self, device_key, device_token, SaveDeviceToken) - } - } else { - device_key!(self, device_key, device_token, SaveDeviceToken) - }; - - Ok((key, token, date, valid_date)) - } - - /// Begin a Microsoft authentication flow. - #[tracing::instrument(skip(self))] - pub async fn begin(&mut self) -> crate::Result { - let (key, token, current_date, valid_date) = self.refresh(Utc::now(), false).await?; - let verify = generate_oauth_challenge(); - let mut hash = sha2::Sha256::new(); - hash.update(&verify); - let hash_result = hash.finalize(); - let challenge = base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(hash_result); - - match sisu_authenticate(&token.token, &challenge, &key, current_date).await { - Ok((session_id, redirect_uri)) => Ok(MinecraftLogin { - verify, - challenge, - session_id, - redirect_uri: redirect_uri.value.msa_oauth_redirect, - }), - Err(err) => { - if valid_date { - Err(err.into()) - } else { - let (key, token, current_date, _) = self.refresh(Utc::now(), false).await?; - let verify = generate_oauth_challenge(); - let mut hash = sha2::Sha256::new(); - hash.update(&verify); - let hash_result = hash.finalize(); - let challenge = base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(hash_result); - let (session_id, redirect_uri) = - sisu_authenticate(&token.token, &challenge, &key, current_date).await?; - - Ok(MinecraftLogin { - verify, - challenge, - session_id, - redirect_uri: redirect_uri.value.msa_oauth_redirect, - }) - } - } - } - } - - /// Finish a Microsoft authentication flow. - #[tracing::instrument(skip(self))] - pub async fn finish( - &mut self, - code: &str, - flow: MinecraftLogin, - ) -> crate::Result { - let (key, token, _, _) = self.refresh(Utc::now(), false).await?; - let oauth_token = oauth_token(code, &flow.verify).await?; - let sisu_authorize = sisu_authorize( - Some(&flow.session_id), - &oauth_token.value.access_token, - &token.token, - &key, - oauth_token.date, - ) - .await?; - let xbox_token = xsts_authorize( - sisu_authorize.value, - &token.token, - &key, - sisu_authorize.date, - ) - .await?; - let minecraft_token = minecraft_token(xbox_token.value).await?; - minecraft_entitlements(&minecraft_token.access_token).await?; - let profile = minecraft_profile(&minecraft_token.access_token).await?; - let profile_id = profile.id.unwrap_or_default(); - let credentials = MinecraftCredentials { - id: profile_id, - username: profile.name, - access_token: minecraft_token.access_token, - refresh_token: oauth_token.value.refresh_token, - #[allow(deprecated)] - expires: oauth_token.date - + chrono::TimeDelta::seconds(oauth_token.value.expires_in as i64), - }; - - self.users.insert(profile_id, credentials.clone()); - - if self.default_user.is_none() { - self.default_user = Some(profile_id); - } - - self.save().await?; - - Ok(credentials) - } - - /// Refresh the current stored [`MinecraftCredentials`]. - async fn refresh_token( - &mut self, - creds: &MinecraftCredentials, - ) -> crate::Result> { - let cred_id = creds.id; - let cred_name = creds.username.clone(); - let oauth_token = oauth_refresh(&creds.refresh_token).await?; - let (key, token, current_date, _) = self.refresh(oauth_token.date, false).await?; - let sisu_authorize = sisu_authorize( - None, - &oauth_token.value.access_token, - &token.token, - &key, - current_date, - ) - .await?; - let xbox_token = xsts_authorize( - sisu_authorize.value, - &token.token, - &key, - sisu_authorize.date, - ) - .await?; - let minecraft_token = minecraft_token(xbox_token.value).await?; - let val = MinecraftCredentials { - id: cred_id, - username: cred_name, - access_token: minecraft_token.access_token, - refresh_token: oauth_token.value.refresh_token, - #[allow(deprecated)] - expires: oauth_token.date - + chrono::TimeDelta::seconds(oauth_token.value.expires_in as i64), - }; - - self.users.insert(val.id, val.clone()); - self.save().await?; - - Ok(Some(val)) - } - - /// Get the default user account as a set of optional [`MinecraftCredentials`]. - #[tracing::instrument(skip(self))] - pub async fn get_default(&mut self) -> crate::Result> { - let credentials = if let Some(default_user) = self.default_user { - if let Some(creds) = self.users.get(&default_user) { - Some(creds) - } else { - self.users.values().next() - } - } else { - self.users.values().next() - }; - - if let Some(creds) = credentials { - if self.default_user != Some(creds.id) { - self.default_user = Some(creds.id); - self.save().await?; - } - - if creds.expires < Utc::now() { - let old_creds = creds.clone(); - let res = self.refresh_token(&old_creds).await; - - match res { - Ok(val) => Ok(val), - Err(err) => { - if let crate::ErrorKind::AuthError(MinecraftAuthError::RequestError { - ref source, - .. - }) = *err.raw - { - if source.is_connect() || source.is_timeout() { - return Ok(Some(old_creds)); - } - } - - Err(err) - } - } - } else { - Ok(Some(creds.clone())) - } - } else { - Ok(None) - } - } - - /// Remove a set of [`MinecraftCredentials`] from a Uuid. - #[tracing::instrument(skip(self))] - pub async fn remove(&mut self, id: Uuid) -> crate::Result> { - let val = self.users.remove(&id); - - if self.default_user == Some(id) { - self.default_user = None; - } - - self.save().await?; - Ok(val) - } -} - -/// A [`reqwest::Request`] with a [`DateTime`] attached to it. -struct RequestWithDate { - pub date: DateTime, - pub value: T, -} - -/// A device token used for Microsoft authentication. -#[derive(Serialize, Deserialize, Clone, Debug)] -#[serde(rename_all = "PascalCase")] -pub struct DeviceToken { - /// The time that the token was issued in [`DateTime`]. - pub issue_instant: DateTime, - /// The expiration date of this token in [`DateTime`]. - pub not_after: DateTime, - /// The token as a string. - pub token: String, - /// The JSON-encoded [`HashMap`] of display claims. - pub display_claims: HashMap, -} - -/// A transferrable EC key translated to a [`SaveDeviceToken`] -#[derive(Debug)] -pub struct DeviceKey { - /// The ID of this device key. - pub id: String, - /// The EC private key. - pub key: SigningKey, - /// The EC `x` value. - pub x: String, - /// The EC `y` value. - pub y: String, -} - -/// A serializable device token that is used to finish authenticaton. -#[derive(Serialize, Deserialize, Debug)] -pub struct SaveDeviceToken { - /// The ID of the token. - pub id: String, - /// The private key associated with the token. - pub private_key: String, - /// The EC `x` value of the key. - pub x: String, - /// The EC `y` value of the key. - pub y: String, - /// The assocated [`DeviceToken`]. - pub token: DeviceToken, -} - -/// Core variables passed throughout the Minecraft login flow. -#[derive(Serialize, Deserialize, Debug)] -#[cfg_attr(feature = "specta", derive(specta::Type))] -pub struct MinecraftLogin { - pub verify: String, - /// The OAuth challenge generated at the start of the flow. - pub challenge: String, - /// The generated session id. - pub session_id: String, - /// The xboxlive redirect URI. - pub redirect_uri: String, -} - -/// A structure of all needed Minecraft credentials for logging in and account management. -#[derive(Serialize, Deserialize, Debug, Clone)] -#[cfg_attr(feature = "specta", derive(specta::Type))] -pub struct MinecraftCredentials { - /// The uuid of the credentials. - pub id: Uuid, - /// The username of the Minecraft account. - pub username: String, - /// The access token as a String. - pub access_token: String, - /// The refresh token as a string for [`MinecraftState#refresh`]. - pub refresh_token: String, - /// The time that the access token expires as a [`DateTime`]. - pub expires: DateTime, -} - -#[tracing::instrument(skip(key))] -pub async fn device_token( - key: &DeviceKey, - current_date: DateTime, -) -> Result, MinecraftAuthError> { - let res = send_signed_request( - None, - "https://device.auth.xboxlive.com/device/authenticate", - "/device/authenticate", - json!({ - "Properties": { - "AuthMethod": "ProofOfPossession", - "Id": format!("{{{}}}", key.id), - "DeviceType": "Win32", - "Version": "10.16.0", - "ProofKey": { - "kty": "EC", - "x": key.x, - "y": key.y, - "crv": "P-256", - "alg": "ES256", - "use": "sig" - } - }, - "RelyingParty": "http://auth.xboxlive.com", - "TokenType": "JWT" - }), - key, - MinecraftAuthStep::DeviceToken, - current_date, - ) - .await?; - - Ok(RequestWithDate { - date: res.current_date, - value: res.body, - }) -} - -#[derive(Deserialize)] -#[serde(rename_all = "PascalCase")] -struct RedirectUri { - pub msa_oauth_redirect: String, -} - -#[tracing::instrument(skip(key))] -async fn sisu_authenticate( - token: &str, - challenge: &str, - key: &DeviceKey, - current_date: DateTime, -) -> Result<(String, RequestWithDate), MinecraftAuthError> { - // what was microsoft on when writing this?????????? - let res = send_signed_request::( - None, - "https://sisu.xboxlive.com/authenticate", - "/authenticate", - json!({ - "AppId": MINECRAFT_CLIENT_ID, - "DeviceToken": token, - "Offers": [MINECRAFT_SCOPES], - "Query": { - "code_challenge": challenge, - "code_challenge_method": "S256", - "state": generate_oauth_challenge(), - "prompt": "select_account" - }, - "RedirectUri": MINECRAFT_REDIRECT_URL, - "Sandbox": "RETAIL", - "TokenType": "code", - "TitleId": "1794566092" - }), - key, - MinecraftAuthStep::SisuAuthenicate, - current_date, - ) - .await?; - - let session_id = res - .headers - .get("X-SessionId") - .and_then(|x| x.to_str().ok()) - .ok_or_else(|| MinecraftAuthError::SessionIdError)? - .to_string(); - - Ok(( - session_id, - RequestWithDate { - date: res.current_date, - value: res.body, - }, - )) -} - -#[derive(Deserialize)] -struct OAuthToken { - // pub token_type: String, - pub expires_in: u64, - // pub scope: String, - pub access_token: String, - pub refresh_token: String, - // pub user_id: String, - // pub foci: String, -} - -#[tracing::instrument] -async fn oauth_token( - code: &str, - verify: &str, -) -> Result, MinecraftAuthError> { - let mut query = HashMap::new(); - query.insert("client_id", MINECRAFT_CLIENT_ID); - query.insert("code", code); - query.insert("code_verifier", verify); - query.insert("grant_type", "authorization_code"); - query.insert("redirect_uri", MINECRAFT_REDIRECT_URL); - query.insert("scope", MINECRAFT_SCOPES); - let res = auth_retry(|| { - crate::utils::http::REQWEST_CLIENT - .post("https://login.live.com/oauth20_token.srf") - .header("Accept", "application/json") - .form(&query) - .send() - }) - .await - .map_err(|source| MinecraftAuthError::RequestError { - source, - step: MinecraftAuthStep::OAuthToken, - })?; - - let status = res.status(); - let current_date = get_date_header(res.headers()); - let text = res - .text() - .await - .map_err(|source| MinecraftAuthError::RequestError { - step: MinecraftAuthStep::OAuthToken, - source, - })?; - - let body = - serde_json::from_str(&text).map_err(|source| MinecraftAuthError::DeserializeError { - step: MinecraftAuthStep::OAuthToken, - raw: text, - source, - status_code: status, - })?; - - Ok(RequestWithDate { - date: current_date, - value: body, - }) -} - -#[tracing::instrument] -async fn oauth_refresh( - refresh_token: &str, -) -> Result, MinecraftAuthError> { - let mut query = HashMap::new(); - query.insert("client_id", MINECRAFT_CLIENT_ID); - query.insert("refresh_token", refresh_token); - query.insert("grant_type", "refresh_token"); - query.insert("redirect_uri", MINECRAFT_REDIRECT_URL); - query.insert("scope", MINECRAFT_SCOPES); - let res = auth_retry(|| { - crate::utils::http::REQWEST_CLIENT - .post("https://login.live.com/oauth20_token.srf") - .header("Accept", "application/json") - .form(&query) - .send() - }) - .await - .map_err(|source| MinecraftAuthError::RequestError { - step: MinecraftAuthStep::RefreshOAuthToken, - source, - })?; - - let status = res.status(); - let current_date = get_date_header(res.headers()); - let text = res - .text() - .await - .map_err(|source| MinecraftAuthError::RequestError { - step: MinecraftAuthStep::RefreshOAuthToken, - source, - })?; - - let body = - serde_json::from_str(&text).map_err(|source| MinecraftAuthError::DeserializeError { - step: MinecraftAuthStep::RefreshOAuthToken, - raw: text, - source, - status_code: status, - })?; - - Ok(RequestWithDate { - date: current_date, - value: body, - }) -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "PascalCase")] -struct SisuAuthorize { - // pub authorization_token: DeviceToken, - // pub device_token: String, - // pub sandbox: String, - pub title_token: DeviceToken, - pub user_token: DeviceToken, - // pub web_page: string, -} - -#[tracing::instrument(skip(key))] -async fn sisu_authorize( - session_id: Option<&str>, - access_token: &str, - device_token: &str, - key: &DeviceKey, - current_date: DateTime, -) -> Result, MinecraftAuthError> { - let res = send_signed_request( - None, - "https://sisu.xboxlive.com/authorize", - "/authorize", - json!({ - "AccessToken": format!("t={access_token}"), - "AppId": MINECRAFT_CLIENT_ID, - "DeviceToken": device_token, - "ProofKey": { - // kibty (kty) - "kty": "EC", - "x": key.x, - "y": key.y, - "crv": "P-256", - "alg": "ES256", - "use": "sig" - }, - "Sandbox": "RETAIL", - "SessionId": session_id, - "SiteName": "user.auth.xboxlive.com", - "RelyingParty": "http://xboxlive.com", - "UseModernGamertag": true - }), - key, - MinecraftAuthStep::SisuAuthorize, - current_date, - ) - .await?; - - Ok(RequestWithDate { - date: res.current_date, - value: res.body, - }) -} - -#[tracing::instrument(skip(key))] -async fn xsts_authorize( - authorize: SisuAuthorize, - device_token: &str, - key: &DeviceKey, - current_date: DateTime, -) -> Result, MinecraftAuthError> { - let res = send_signed_request( - None, - "https://xsts.auth.xboxlive.com/xsts/authorize", - "/xsts/authorize", - json!({ - "RelyingParty": "rp://api.minecraftservices.com/", - "TokenType": "JWT", - "Properties": { - "SandboxId": "RETAIL", - "UserTokens": [authorize.user_token.token], - "DeviceToken": device_token, - "TitleToken": authorize.title_token.token, - }, - }), - key, - MinecraftAuthStep::XstsAuthorize, - current_date, - ) - .await?; - - Ok(RequestWithDate { - date: res.current_date, - value: res.body, - }) -} - -#[derive(Deserialize)] -struct MinecraftToken { - // pub username: String, - pub access_token: String, - // pub token_type: String, - // pub expires_in: u64, -} - -#[tracing::instrument] -async fn minecraft_token(token: DeviceToken) -> Result { - let uhs = token - .display_claims - .get("xui") - .and_then(|x| x.get(0)) - .and_then(|x| x.get("uhs")) - .and_then(|x| x.as_str().map(String::from)) - .ok_or_else(|| MinecraftAuthError::HashError)?; - - let token = token.token; - let res = auth_retry(|| { - crate::utils::http::REQWEST_CLIENT - .post("https://api.minecraftservices.com/launcher/login") - .header("Accept", "application/json") - .json(&json!({ - "platform": "PC_LAUNCHER", - "xtoken": format!("XBL3.0 x={uhs};{token}"), - })) - .send() - }) - .await - .map_err(|source| MinecraftAuthError::RequestError { - step: MinecraftAuthStep::MinecraftToken, - source, - })?; - - let status = res.status(); - let text = res - .text() - .await - .map_err(|source| MinecraftAuthError::RequestError { - step: MinecraftAuthStep::MinecraftToken, - source, - })?; - - serde_json::from_str(&text).map_err(|source| MinecraftAuthError::DeserializeError { - step: MinecraftAuthStep::MinecraftToken, - raw: text, - source, - status_code: status, - }) -} - -#[derive(Deserialize)] -struct MinecraftProfile { - pub id: Option, - pub name: String, -} - -#[tracing::instrument] -async fn minecraft_profile(token: &str) -> Result { - let res = auth_retry(|| { - crate::utils::http::REQWEST_CLIENT - .get("https://api.minecraftservices.com/minecraft/profile") - .header("Accept", "application/json") - .bearer_auth(token) - .send() - }) - .await - .map_err(|source| MinecraftAuthError::RequestError { - step: MinecraftAuthStep::MinecraftProfile, - source, - })?; - - let status = res.status(); - let text = res - .text() - .await - .map_err(|source| MinecraftAuthError::RequestError { - step: MinecraftAuthStep::MinecraftProfile, - source, - })?; - - serde_json::from_str(&text).map_err(|source| MinecraftAuthError::DeserializeError { - step: MinecraftAuthStep::MinecraftProfile, - raw: text, - source, - status_code: status, - }) -} - -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -struct MinecraftEntitlements {} - -#[tracing::instrument] -async fn minecraft_entitlements(token: &str) -> Result { - let res = auth_retry(|| { - crate::utils::http::REQWEST_CLIENT - .get("https://api.minecraftservices.com/entitlements/mcstore") - .header("Accept", "application/json") - .bearer_auth(token) - .send() - }) - .await - .map_err(|source| MinecraftAuthError::RequestError { - step: MinecraftAuthStep::MinecraftEntitlements, - source, - })?; - - let status = res.status(); - let text = res - .text() - .await - .map_err(|source| MinecraftAuthError::RequestError { - step: MinecraftAuthStep::MinecraftEntitlements, - source, - })?; - - serde_json::from_str(&text).map_err(|source| MinecraftAuthError::DeserializeError { - step: MinecraftAuthStep::MinecraftEntitlements, - raw: text, - source, - status_code: status, - }) -} - -#[tracing::instrument(skip(request_fn))] -async fn auth_retry(request_fn: impl Fn() -> F) -> Result -where - F: std::future::Future>, -{ - const RETRY_COUNT: usize = 5; - const RETRY_WAIT: tokio::time::Duration = tokio::time::Duration::from_millis(250); - let mut resp = request_fn().await; - - for i in 0..RETRY_COUNT { - match &resp { - Ok(_) => { - break; - } - Err(err) => { - if err.is_connect() || err.is_timeout() { - if i < RETRY_COUNT - 1 { - tracing::debug!( - "request failed due to a connection error, retrying request..." - ); - tokio::time::sleep(RETRY_WAIT).await; - resp = request_fn().await; - } else { - break; - } - } - } - } - } - - resp -} - -#[tracing::instrument] -fn device_key() -> Result { - let id = Uuid::new_v4().to_string().to_uppercase(); - let signing_key = SigningKey::random(&mut rand::rngs::OsRng); - let public_key = p256::ecdsa::VerifyingKey::from(&signing_key); - let encoded_point = public_key.to_encoded_point(false); - - Ok(DeviceKey { - id, - key: signing_key, - x: base64::prelude::BASE64_STANDARD_NO_PAD.encode( - encoded_point - .x() - .ok_or_else(|| MinecraftAuthError::PublicKeyReading)?, - ), - y: base64::prelude::BASE64_STANDARD_NO_PAD.encode( - encoded_point - .y() - .ok_or_else(|| MinecraftAuthError::PublicKeyReading)?, - ), - }) -} - -struct SignedRequestResponse { - pub headers: reqwest::header::HeaderMap, - pub current_date: DateTime, - pub body: T, -} - -async fn send_signed_request( - authorization: Option<&str>, - url: &str, - url_path: &str, - raw_body: serde_json::Value, - key: &DeviceKey, - step: MinecraftAuthStep, - current_date: DateTime, -) -> Result, MinecraftAuthError> { - use byteorder::WriteBytesExt; - let auth = authorization.map_or(Vec::new(), |v| v.as_bytes().to_vec()); - let body = serde_json::to_vec(&raw_body) - .map_err(|source| MinecraftAuthError::SerializeError { step, source })?; - let time: u128 = { ((current_date.timestamp() as u128) + 11_644_473_600) * 10_000_000 }; - - let mut buffer = Vec::new(); - buffer - .write_u32::(1) - .map_err(|source| MinecraftAuthError::SigningError { step, source })?; - buffer - .write_u8(0) - .map_err(|source| MinecraftAuthError::SigningError { step, source })?; - buffer - .write_u64::(time as u64) - .map_err(|source| MinecraftAuthError::SigningError { step, source })?; - buffer - .write_u8(0) - .map_err(|source| MinecraftAuthError::SigningError { step, source })?; - buffer.extend_from_slice(b"POST"); - buffer - .write_u8(0) - .map_err(|source| MinecraftAuthError::SigningError { step, source })?; - buffer.extend_from_slice(url_path.as_bytes()); - buffer - .write_u8(0) - .map_err(|source| MinecraftAuthError::SigningError { step, source })?; - buffer.extend_from_slice(&auth); - buffer - .write_u8(0) - .map_err(|source| MinecraftAuthError::SigningError { step, source })?; - buffer.extend_from_slice(&body); - buffer - .write_u8(0) - .map_err(|source| MinecraftAuthError::SigningError { step, source })?; - - let ecdsa_sig: p256::ecdsa::Signature = key.key.sign(&buffer); - let mut sig_buffer = Vec::new(); - - sig_buffer - .write_i32::(1) - .map_err(|source| MinecraftAuthError::SigningError { step, source })?; - sig_buffer - .write_u64::(time as u64) - .map_err(|source| MinecraftAuthError::SigningError { step, source })?; - sig_buffer.extend_from_slice(&ecdsa_sig.r().to_bytes()); - sig_buffer.extend_from_slice(&ecdsa_sig.s().to_bytes()); - - let signature = base64::prelude::BASE64_STANDARD.encode(&sig_buffer); - - let res = auth_retry(|| { - let mut request = crate::utils::http::REQWEST_CLIENT - .post(url) - .header("Content-Type", "application/json; charset=utf-8") - .header("Accept", "application/json") - .header("Signature", &signature); - - if url != "https://sisu.xboxlive.com/authorize" { - request = request.header("x-xbl-contract-version", "1"); - } - - if let Some(auth) = authorization { - request = request.header("Authorization", auth); - } - - request.body(body.clone()).send() - }) - .await - .map_err(|source| MinecraftAuthError::RequestError { step, source })?; - - let status = res.status(); - let headers = res.headers().clone(); - let current_date = get_date_header(&headers); - let body = res - .text() - .await - .map_err(|source| MinecraftAuthError::RequestError { step, source })?; - - let body = - serde_json::from_str(&body).map_err(|source| MinecraftAuthError::DeserializeError { - step, - raw: body, - source, - status_code: status, - })?; - Ok(SignedRequestResponse { - headers, - current_date, - body, - }) -} - -#[tracing::instrument] -fn get_date_header(headers: &reqwest::header::HeaderMap) -> DateTime { - headers - .get(reqwest::header::DATE) - .and_then(|x| x.to_str().ok()) - .and_then(|x| DateTime::parse_from_rfc2822(x).ok()) - .map_or_else(Utc::now, |x| x.with_timezone(&Utc)) -} - -#[tracing::instrument] -fn generate_oauth_challenge() -> String { - let mut rng = rand::thread_rng(); - let bytes: Vec = (0..64).map(|_| rng.gen::()).collect(); - bytes.iter().fold(String::new(), |mut output, b| { - let _ = write!(output, "{b:02x}"); - output - }) -} - -/// An ordered list of all MSA authentication steps. -#[derive(Debug, Clone, Copy)] -pub enum MinecraftAuthStep { - DeviceToken, - SisuAuthenicate, - OAuthToken, - RefreshOAuthToken, - SisuAuthorize, - XstsAuthorize, - MinecraftToken, - MinecraftEntitlements, - MinecraftProfile, -} - -/// Wrapper around all `Error`s that can occur during the Microsoft authentication process. -#[derive(thiserror::Error, Debug)] -pub enum MinecraftAuthError { - #[error("failed to read public key during key generation")] - PublicKeyReading, - #[error("failed to serialize private key using PKCS8: {0}")] - PKCS8Error(#[from] p256::pkcs8::Error), - #[error("failed to serialize JSON during MSA step {step:?}: {source}")] - SerializeError { - step: MinecraftAuthStep, - #[source] - source: serde_json::Error, - }, - #[error("failed to deserialize JSON during MSA step {step:?}: {source}! status code {status_code} - body: {raw}")] - DeserializeError { - step: MinecraftAuthStep, - raw: String, - #[source] - source: serde_json::Error, - status_code: reqwest::StatusCode, - }, - #[error("failed to request using HTTP during MSA step {step:?}: {source}")] - RequestError { - step: MinecraftAuthStep, - #[source] - source: reqwest::Error, - }, - #[error("failed to create signed buffer during MSA step {step:?}: {source}")] - SigningError { - step: MinecraftAuthStep, - #[source] - source: std::io::Error, - }, - #[error("failed to read user hash")] - HashError, - #[error("failed to read user xbox session ID")] - SessionIdError, -} diff --git a/packages/core_old/src/store/mod.rs b/packages/core_old/src/store/mod.rs deleted file mode 100644 index 83e0772a..00000000 --- a/packages/core_old/src/store/mod.rs +++ /dev/null @@ -1,289 +0,0 @@ -//! **`OneLauncher` Store** -//! -//! Core state and storage management for the launcher, managing all states. - -use std::sync::Arc; -use std::time::Duration; - -use notify::RecommendedWatcher; -use notify_debouncer_mini::Debouncer; -use tokio::sync::{OnceCell, RwLock, Semaphore}; - -use crate::proxy::send::{init_ingress_internal, send_ingress, send_offline}; -use crate::utils::http; -use crate::utils::http::{FetchSemaphore, IoSemaphore}; - -mod dirs; -pub use self::dirs::*; - -mod minecraft; -pub use self::minecraft::*; - -mod credentials; -pub use self::credentials::*; - -mod discord; -pub use self::discord::*; - -mod metadata; -pub use self::metadata::*; - -mod settings; -pub use self::settings::*; - -mod java_versions; -pub use self::java_versions::*; - -mod processor; -pub use self::processor::*; - -mod clusters; -pub use self::clusters::*; - -mod package; -pub use self::package::*; - -mod oneconfig; -pub use self::oneconfig::*; - -/// The static [`OnceCell>`] for storing the global runtime launcher state. -static LAUNCHER_STATE: OnceCell> = OnceCell::const_new(); - -/// The public `OneLauncher` state structure. -pub struct State { - /// Handles a boolean to check if the launcher is connected to the internet - pub offline: RwLock, - /// Handles core launcher directory and files - pub directories: Directories, - - /// Semaphore used to handle concurrent network requests - pub fetch_semaphore: FetchSemaphore, - /// Maximum number of semaphore permits to limit the amount of network requests at one time - pub fetch_semaphore_max: RwLock, - /// Semaphore used to handle concurrent IO requests - pub io_semaphore: IoSemaphore, - /// Maximum number of semaphore permits to limit the amount of IO requests at one time - pub io_semaphore_max: RwLock, - - /// Handles launcher metadata via [`interpulse`] - pub metadata: RwLock, - /// Handles launcher configuration and settings - pub settings: RwLock, - /// Handles minecraft auth flows and users - pub users: RwLock, - /// Handles processes - pub processor: RwLock, - /// Handles `OneConfig` integration with [`async-tungstenite`] - pub oneconfig: RwLock>, - /// Handles secure credential management with [`iota_stronghold`]. - pub credentials: RwLock>, - /// Handles clusters - pub(crate) clusters: RwLock, - /// Handles packages - pub(crate) packages: RwLock, - /// Handles internal ingress processes - pub(crate) ingress_processor: RwLock, - /// Handles file system watching for cluster manager - pub(crate) watcher: RwLock>, - /// Handles Discord rich prescence - pub discord_rpc: DiscordRPC, -} - -impl State { - /// Get the current global launcher state (or initialize it) - pub async fn get() -> crate::Result>> { - Ok(Arc::new( - LAUNCHER_STATE - .get_or_try_init(Self::initialize) - .await? - .read() - .await, - )) - } - - /// Get and lock writing to the current global launcher state (or initalize it) - pub async fn get_and_write() -> crate::Result> { - Ok(LAUNCHER_STATE - .get_or_try_init(Self::initialize) - .await? - .write() - .await) - } - - /// Initializes the core OneLauncher state fully. - #[tracing::instrument] - #[onelauncher_macros::memory] - async fn initialize() -> crate::Result> { - let ingress = - init_ingress_internal(crate::IngressType::Initialize, 100.0, "initializing state") - .await?; - - let settings = Settings::initialize(&Directories::init_settings_file()?).await?; - let directories = Directories::initalize(&settings)?; - send_ingress(&ingress, 10.0, None).await?; - let mut watcher = crate::utils::watcher::initialize_watcher()?; - let fetch_semaphore = - FetchSemaphore(RwLock::new(Semaphore::new(settings.max_async_fetches))); - let io_semaphore = IoSemaphore(RwLock::new(Semaphore::new( - settings.max_async_io_operations, - ))); - send_ingress(&ingress, 10.0, None).await?; - - let is_offline = !http::check_internet_connection(3).await; - - // TODO: Make this run in the background, this delays launcher startup by a couple seconds - let metadata_in = - Metadata::initialize(&directories, !is_offline, &io_semaphore, &fetch_semaphore); - let clusters_in = Clusters::initialize(&directories, &mut watcher); - let users_in = MinecraftState::initialize(&directories, &io_semaphore); - let (metadata, clusters, users) = crate::ingress_join! { - Some(&ingress), 70.0, Some("loading core"); - metadata_in, - clusters_in, - users_in, - }?; - - send_ingress(&ingress, 5.0, Some("loading packages")).await?; - let packages = Packages::initialize(&clusters); - - let ingress_processor = IngressProcessor::new(); - let discord_rpc = DiscordRPC::initialize(is_offline || settings.disable_discord)?; - if !settings.disable_discord && !is_offline { - let _ = discord_rpc.apply_activity("Idling...", true).await; - } - - let processor = Processor::new(); - - Self::status_loop(); - send_ingress(&ingress, 10.0, None).await?; - - Ok::, crate::Error>(RwLock::new(Self { - offline: RwLock::new(is_offline), - directories, - fetch_semaphore, - fetch_semaphore_max: RwLock::new(settings.max_async_fetches as u32), - io_semaphore, - io_semaphore_max: RwLock::new(settings.max_async_io_operations as u32), - metadata: RwLock::new(metadata), - settings: RwLock::new(settings), - users: RwLock::new(users), - processor: RwLock::new(processor), - oneconfig: RwLock::new(None), - credentials: RwLock::new(None), - clusters: RwLock::new(clusters), - packages: RwLock::new(packages), - ingress_processor: RwLock::new(ingress_processor), - watcher: RwLock::new(watcher), - discord_rpc, - })) - } - - /// Initalizes a "game loop" which checks for updates and if we are online. - pub fn status_loop() { - tokio::task::spawn(async { - loop { - let state = Self::get().await; - if let Ok(state) = state { - let _ = state.check_status().await; - } - - tokio::time::sleep(Duration::from_secs(10)).await; - } - }); - } - - /// Updates all data if we are connected to the internet. - pub fn update() { - tokio::task::spawn(async { - let state = Self::get(); - if let Ok(state) = state.await { - if !*state.offline.read().await { - let version_up = Clusters::update_versions(); - let meta_up = Metadata::update(); - let package_up = Clusters::update_packages(); - - let _ = tokio::join!(version_up, meta_up, package_up); - } - } - }); - } - - /// Synchronizes data that can change outside of our control. - #[tracing::instrument] - #[onelauncher_macros::memory] - pub async fn sync() -> crate::Result<()> { - let state = Self::get().await?; - let sync_settings = async { - let state = Arc::clone(&state); - tokio::spawn(async move { - let reader = state.settings.read().await; - reader.sync(&state.directories.settings_file()).await?; - Ok::<_, crate::Error>(()) - }) - .await? - }; - - let sync_clusters = async { - let state = Arc::clone(&state); - - tokio::spawn(async move { - let clusters = state.clusters.read().await; - - clusters.sync().await?; - Ok::<_, crate::Error>(()) - }) - .await? - }; - - tokio::try_join!(sync_settings, sync_clusters)?; - Ok(()) - } - - /// Resets the IO [`Semaphore`] to clear all tasks. - /// # This should ONLY be called when we can ensure - /// # that there are no current tasks running!! - pub async fn reset_io_semaphore(&self) { - let settings = self.settings.read().await; - let mut io_semaphore = self.io_semaphore.0.write().await; - let mut total_permits = self.io_semaphore_max.write().await; - // block ALL semaphore permits - this can be destructive - let _ = io_semaphore.acquire_many(*total_permits).await; - - io_semaphore.close(); - *total_permits = settings.max_async_io_operations as u32; - *io_semaphore = Semaphore::new(settings.max_async_io_operations); - } - - /// Resets the Fetch [`Semaphore`] to clear all tasks. - /// # This should ONLY be called when we can ensure - /// # that there are no current tasks running!! - pub async fn reset_fetch_semaphore(&self) { - let settings = self.settings.read().await; - let mut fetch_semaphore = self.fetch_semaphore.0.write().await; - let mut total_permits = self.fetch_semaphore_max.write().await; - // block ALL semaphore permits - this can be destructive - let _ = fetch_semaphore.acquire_many(*total_permits).await; - - fetch_semaphore.close(); - *total_permits = settings.max_async_io_operations as u32; - *fetch_semaphore = Semaphore::new(settings.max_async_fetches); - } - - /// Checks if we are online used in a loop. - pub async fn check_status(&self) -> crate::Result<()> { - let is_online = http::check_internet_connection(3).await; - let mut offline = self.offline.write().await; - if *offline != is_online { - return Ok(()); - } - - send_offline(!is_online).await?; - *offline = !is_online; - Ok(()) - } - - /// Determine if the global state is initalized or not (wrapper) - pub fn initalized() -> bool { - LAUNCHER_STATE.initialized() - } -} diff --git a/packages/core_old/src/store/oneconfig.rs b/packages/core_old/src/store/oneconfig.rs deleted file mode 100644 index 1057eae3..00000000 --- a/packages/core_old/src/store/oneconfig.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! `OneConfig` state using [`async_tungstenite`] to communicate to the `OneConfig` server - -use super::ClusterPath; -use serde::{Deserialize, Serialize}; -use uuid::Uuid; - -/// Wrapper over an [`async_tungstenite::WebSocketStream`] to connect to the `OneConfig` server. -pub struct OneConfig { - /// The public tokio socket connection. - pub socket: async_tungstenite::WebSocketStream, -} - -/// A WebSocket packet while updating a mod to display a GUI in game. -#[derive(Serialize, Deserialize, Debug)] -pub struct ModUpdatePacket { - /// The ID of the packet. - id: Uuid, - /// The ID of the mod being updated. - mod_id: Uuid, - /// The [`ClusterPath`] associated with this event. - cluster_path: ClusterPath, -} - -impl OneConfig { - /// Initializes a `OneConfig` socket connection on localhost port `4023`. - pub async fn initialize(cluster: ClusterPath) -> crate::Result { - let (socket, _) = async_tungstenite::tokio::connect_async(format!( - "wss://localhost:4023/oneconfig/ws?cluster={cluster}" - )) - .await?; - Ok(Self { socket }) - } - - pub async fn close(&mut self) -> crate::Result<()> { - self.socket.close(None).await?; - - Ok(()) - } -} diff --git a/packages/core_old/src/store/package.rs b/packages/core_old/src/store/package.rs deleted file mode 100644 index 5b1b9f1b..00000000 --- a/packages/core_old/src/store/package.rs +++ /dev/null @@ -1,969 +0,0 @@ -//! Handlers for Mod metadata that can be displayed in a GUI mod list or exported as a mod pack - -use crate::package::content::Providers; -use crate::proxy::send::{init_ingress, send_ingress}; -use crate::proxy::IngressId; -use crate::store::Loader; -use crate::utils::crypto; -use crate::utils::http::{write_icon, IoSemaphore}; -use crate::State; -use async_zip::tokio::read::fs::ZipFileReader; -use chrono::{DateTime, Utc}; -use onelauncher_utils::io; -use serde::{Deserialize, Serialize}; -use std::collections::{HashMap, HashSet}; -use std::fmt::Display; -use std::path::{Path, PathBuf}; -use std::vec; -use tokio::fs::DirEntry; - -use super::{ClusterPath, Clusters, Directories, PackagePath}; - -/// Represents types of packages handled by the launcher. -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash)] -#[serde(rename_all = "lowercase")] -pub enum PackageType { - /// Represents a mod jarfile - Mod, - /// Represents a datapack file - DataPack, - /// Represents a resourcepack file - ResourcePack, - /// Represents a shaderpack file - #[serde(alias = "shader")] - ShaderPack, - /// Represents a modpack file - ModPack, -} - -impl PackageType { - /// attempt to get a [`PackageType`] from a loader string. - #[must_use] - pub fn from_loader(loaders: &[String]) -> Option { - if loaders - .iter() - .any(|x| ["fabric", "forge", "quilt"].contains(&&**x)) - { - Some(Self::Mod) - } else if loaders.iter().any(|x| x == "datapack") { - Some(Self::DataPack) - } else if loaders.iter().any(|x| ["iris", "optifine"].contains(&&**x)) { - Some(Self::ShaderPack) - } else if loaders - .iter() - .any(|x| ["vanilla", "canvas", "minecraft"].contains(&&**x)) - { - Some(Self::ResourcePack) - } else { - None - } - } - - #[must_use] - pub fn from_parent(path: &Path) -> Option { - let path = path.parent()?.file_name()?; - match path.to_str()? { - "mods" => Some(Self::Mod), - "datapacks" => Some(Self::DataPack), - "resourcepacks" => Some(Self::ResourcePack), - "shaderpacks" => Some(Self::ShaderPack), - "modpack" => Some(Self::ModPack), - _ => None, - } - } - - #[must_use] - pub const fn get_name(&self) -> &'static str { - match self { - Self::Mod => "mod", - Self::DataPack => "datapack", - Self::ResourcePack => "resourcepack", - Self::ShaderPack => "shader", - Self::ModPack => "modpack", - } - } - - #[must_use] - pub const fn get_folder(&self) -> &'static str { - match self { - Self::Mod => "mods", - Self::DataPack => "datapacks", - Self::ResourcePack => "resourcepacks", - Self::ShaderPack => "shaderpacks", - Self::ModPack => "modpack", - } - } - - #[must_use] - pub fn get_meta(&self) -> PathBuf { - PathBuf::from(format!( - "{}/{}", - self.get_folder(), - self.get_meta_file_name() - )) - } - - #[must_use] - pub fn get_meta_file_name(&self) -> String { - String::from(".packages.json") - } - - pub async fn file_matches(&self, entry: &DirEntry) -> crate::Result { - if !(entry.path().try_exists()?) { - return Ok(false); - } - - Ok(match self { - Self::Mod => { - entry.file_type().await?.is_file() - && entry.path().extension() == Some("jar".as_ref()) - } - _ => false, - }) - } - - pub fn iterator() -> impl Iterator { - [ - Self::Mod, - Self::DataPack, - Self::ResourcePack, - Self::ShaderPack, - Self::ModPack, - ] - .iter() - .copied() - } -} - -impl Display for PackageType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.get_name()) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[cfg_attr(feature = "specta", derive(specta::Type))] -pub struct SearchResult { - pub slug: String, - pub title: String, - pub description: String, - #[serde(default)] - pub categories: Vec, - pub client_side: PackageSide, - pub server_side: PackageSide, - pub project_type: PackageType, - pub downloads: u64, - #[serde(default)] - pub icon_url: String, - pub project_id: String, - pub author: String, - pub versions: Vec, - pub follows: u32, - pub date_created: Option>, - pub date_modified: Option>, -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -#[cfg_attr(feature = "specta", derive(specta::Type))] -pub struct ProviderSearchResults { - pub provider: Providers, - pub results: Vec, - pub total: u32, -} - -/// A struct that represents a Package. -#[derive(Serialize, Deserialize, Clone, Debug)] -#[cfg_attr(feature = "specta", derive(specta::Type))] -pub struct Package { - pub sha1: String, - pub meta: PackageMetadata, - pub file_name: String, - pub disabled: bool, -} - -impl Package { - pub fn new(path: &PathBuf, meta: PackageMetadata) -> crate::Result { - let file_name = path.file_name() - .ok_or_else(|| anyhow::anyhow!("couldn't get filename of path {path:?}"))? - .to_string_lossy() - .to_string(); - let sha1 = crypto::sha1_file(path)?; - - Ok(Self { - sha1, - meta, - file_name, - disabled: false, - }) - } -} - -pub type PackagesMap = HashMap; - -pub struct Packages { - managers: HashMap, -} - -impl Packages { - #[must_use] - pub fn initialize(clusters: &Clusters) -> Self { - let mut this = Self { managers: HashMap::new() }; - - // TODO: This should probably not clone and store the cluster path in like 2 areas - for cluster_path in clusters.0.keys() { - this.add_cluster(cluster_path.clone()); - } - - this - } - - pub fn add_cluster(&mut self, cluster_path: ClusterPath) { - let mgr = PackageManager::new(cluster_path.clone()); - self.managers.insert(cluster_path, mgr); - } - - #[must_use] - pub fn get(&self, cluster_path: &ClusterPath) -> Option<&PackageManager> { - self.managers.get(cluster_path) - } - - pub fn get_mut(&mut self, cluster_path: &ClusterPath) -> Option<&mut PackageManager> { - self.managers.get_mut(cluster_path) - } -} - -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct PackagesMeta { - pub packages: PackagesMap, - pub package_type: PackageType, -} - -impl PackagesMeta { - #[must_use] - pub fn new(package_type: PackageType) -> Self { - Self { - packages: PackagesMap::new(), - package_type, - } - } -} - -#[derive(Debug)] -pub struct PackageManager { - pub cluster_path: ClusterPath, - synced: bool, - modpacks: PackagesMeta, - mods: PackagesMeta, - datapacks: PackagesMeta, - resourcepacks: PackagesMeta, - shaderpacks: PackagesMeta, -} - -impl PackageManager { - #[must_use] - pub fn new(cluster_path: ClusterPath) -> Self { - Self { - cluster_path, - modpacks: PackagesMeta::new(PackageType::ModPack), - synced: false, - mods: PackagesMeta::new(PackageType::Mod), - datapacks: PackagesMeta::new(PackageType::DataPack), - resourcepacks: PackagesMeta::new(PackageType::ResourcePack), - shaderpacks: PackagesMeta::new(PackageType::ShaderPack), - } - } - - pub async fn get_meta_file( - &self, - dirs: &Directories, - package_type: PackageType, - ) -> crate::Result { - Ok(self - .cluster_path - .full_path_dirs(dirs) - .await? - .join(package_type.get_meta())) - } - - #[async_recursion::async_recursion] - async fn get_and_sync(&mut self, dirs: Option<&Directories>) -> &mut Self { - if !self.synced { - self.synced = true; // early set to prevent infinite loop - - let state = State::get().await.expect("couldn't get state when syncing packages"); - let dirs = dirs.map_or_else(|| &state.directories, |dirs| dirs); - - self.sync_packages(dirs).await; - } - - self - } - - /// Get the `PackagesMeta` for a specific package type. Syncs. - #[must_use] - pub async fn get(&mut self, package_type: PackageType) -> &PackagesMeta { - self.get_and_sync(None).await; - match package_type { - PackageType::Mod => &self.mods, - PackageType::DataPack => &self.datapacks, - PackageType::ResourcePack => &self.resourcepacks, - PackageType::ShaderPack => &self.shaderpacks, - PackageType::ModPack => &self.modpacks, - } - } - - /// Get the `PackagesMeta` for a specific package type. Does not sync. - fn get_mut(&mut self, package_type: PackageType) -> &mut PackagesMeta { - match package_type { - PackageType::Mod => &mut self.mods, - PackageType::ModPack => &mut self.modpacks, - PackageType::DataPack => &mut self.datapacks, - PackageType::ResourcePack => &mut self.resourcepacks, - PackageType::ShaderPack => &mut self.shaderpacks, - } - } - - /// Get the PackagesMeta for a specific package type. Does not sync. - #[tracing::instrument] - async fn get_from_file( - &self, - dirs: &Directories, - package_type: PackageType, - ) -> crate::Result { - let packages_meta = &self.get_meta_file(dirs, package_type).await?; - - let meta: PackagesMeta = if packages_meta.exists() && packages_meta.is_file() { - if let Ok(meta) = serde_json::from_slice(&io::read(packages_meta).await?) { - meta - } else { - io::copy(packages_meta, packages_meta.with_extension(".json.bak")).await?; - io::write(packages_meta, "{}").await?; - PackagesMeta::new(package_type) - } - } else { - io::write(packages_meta, "{}").await?; - PackagesMeta::new(package_type) - }; - - Ok(meta) - } - - // add a package to the manager - #[tracing::instrument(skip(self, package))] - pub async fn add_package( - &mut self, - package_path: PackagePath, - package: Package, - ) -> crate::Result<()> { - let package_type = package_path.package_type()?; - let packages = &mut self.get_mut(package_type).packages; - insert_package_by_path(packages, package_path, package); - - Ok(()) - } - - // remove a package to the manager - #[tracing::instrument(skip(self))] - pub async fn remove_package( - &mut self, - package_path: &PackagePath, - ) -> crate::Result<()> { - let package_type = package_path.package_type()?; - let full_path = package_path.full_path(&self.cluster_path).await?; - - io::remove_file(full_path).await?; - - let packages = &mut self.get_mut(package_type).packages; - packages.remove(package_path); - - Ok(()) - } - - // disable a package in the manager - #[tracing::instrument(skip(self))] - pub async fn set_package_enabled( - &mut self, - package_path: &PackagePath, - enabled: bool, - ) -> crate::Result<()> { - let state = State::get().await?; - - let package_type = package_path.package_type()?; - let full_path = package_path.full_path(&self.cluster_path).await?; - - let packages = &mut self.get_mut(package_type).packages; - - let was_disabled = full_path.extension().map_or(false, |e| e == "disabled"); - let mut new_path = full_path.clone(); - - let changed = if enabled && was_disabled { - new_path.set_extension("") // removes .disabled - } else if !enabled && !was_disabled { - new_path.set_extension("jar.disabled") // adds .disabled - } else { - false - }; - - if changed { - io::rename(full_path, new_path.clone()).await?; - - - if let Some(mut package) = packages.remove(package_path) { - let file_name = new_path.file_name().ok_or_else(|| anyhow::anyhow!("couldn't get file name"))?.to_string_lossy().to_string(); - let package_path = PackagePath::new(package_type.clone(), file_name.clone())?; - - package.disabled = !enabled; - package.file_name = file_name; - - packages.insert(package_path, package); - } - - self.sync_to_file_by_type(&state.directories, package_type).await?; - } - - Ok(()) - } - - /// sync all packages - #[tracing::instrument(skip(self, dirs))] - pub async fn sync_packages(&mut self, dirs: &Directories) { - tracing::info!("syncing packages for cluster {}", self.cluster_path); - let package_types = vec![ - PackageType::Mod, - PackageType::DataPack, - PackageType::ResourcePack, - PackageType::ShaderPack, - ]; - - for package_type in package_types { - if let Err(err) = &self.sync_packages_by_type(dirs, package_type, None).await { - tracing::error!("failed to sync package {}: {}", package_type.get_name(), err); - } - } - } - - /// sync packages from the metafile - #[tracing::instrument(skip(self, dirs))] - async fn sync_from_file_by_type( - &mut self, - dirs: &Directories, - package_type: PackageType, - ) -> crate::Result<()> { - let packages_meta = self.get_from_file(dirs, package_type).await?; - self.get_mut(package_type).packages = packages_meta.packages; - - Ok(()) - } - - /// sync packages from the manager to the metafile - #[tracing::instrument(skip(self, dirs))] - async fn sync_to_file_by_type( - &mut self, - dirs: &Directories, - package_type: PackageType, - ) -> crate::Result<()> { - let packages_meta = self.get(package_type).await; - let packages_meta = serde_json::to_string(&packages_meta)?; - - io::write(self.get_meta_file(dirs, package_type).await?, packages_meta).await?; - - Ok(()) - } - - /// returns a list of packages that have a file but are not synced in the manager - #[tracing::instrument(skip(self, dirs, packages))] - async fn sync_packages_from_local( - &self, - dirs: &Directories, - packages: &mut PackagesMap, - package_type: PackageType, - ) -> crate::Result<()> { - let mut files = io::read_dir( - self.cluster_path - .full_path_dirs(dirs) - .await? - .join(package_type.get_folder()), - ) - .await?; - - let mut packages_to_keep = HashSet::::new(); - let mut packages_to_infer = HashSet::::new(); - - let full_cluster_path = self.cluster_path.full_path().await?; - - while let Some(file) = files.next_entry().await? { - // Skip .packages.json meta file - if file.file_name().to_string_lossy().starts_with(&".") || file.file_type().await?.is_dir() - { - continue; - } - - let package_path = PackagePath::from_path(&file.path()).await?; - - // Check if the file is in the packages list already - if packages.get(&package_path).is_none() { - // Package path is not in the packages list but exists on file system, lets try to infer it later on - packages_to_infer.insert(full_cluster_path.join(package_path.relative_path())); - } - - // Add the package to the set to not remove it from the manager - packages_to_keep.insert(package_path); - } - - // Infer any packages - if !packages_to_infer.is_empty() { - let ingress = init_ingress(crate::IngressType::InferPackages, 300.0, format!("inferring {package_type}s").as_str()).await?; - - if let Some(not_found) = infer_provider(&ingress, Providers::Modrinth, package_type, packages, packages_to_infer, crypto::sha1_file).await { - if let Some(not_found) = infer_provider(&ingress, Providers::SkyClient, package_type, packages, not_found, crypto::md5_file).await { - if let Some(not_found) = infer_provider(&ingress, Providers::Curseforge, package_type, packages, not_found, |path| { - let hash = crypto::murmur2_file(path)?; - Ok(hash.to_string()) - }).await { - for path in not_found { - tracing::warn!("failed to infer package: {:?}", path); - - let meta = PackageMetadata::Unknown; - let package = Package::new(&path, meta)?; - let package_path = PackagePath::from_path(&path).await?; - - insert_package_by_path(packages, package_path, package); - } - } - } - } - - let _ = send_ingress(&ingress, 300.0, Some(format!("finished inferring {package_type}s").as_str())).await; - } - - if packages.len() != packages_to_keep.len() { - packages.retain(|pkg_path, _| packages_to_keep.contains(pkg_path)); - } - - Ok(()) - } - - // sync packages in a cluster - #[tracing::instrument(skip(self, dirs))] - pub async fn sync_packages_by_type( - &mut self, - dirs: &Directories, - package_type: PackageType, - clear: Option, - ) -> crate::Result<()> { - if clear.unwrap_or(false) { - self.get_mut(package_type).packages.clear(); - } - - // Clone the current packages and merge them with the local package list - let mut packages = self.get(package_type).await.packages.clone(); - let mut new_packages = self.get_from_file(dirs, package_type).await?.packages; - packages.extend(new_packages.drain()); - - // Sync the packages from FS - self.sync_packages_from_local(dirs, &mut packages, package_type).await?; - - // Finally store the new list in memory and on disk - let stored_packages = &self.get_mut(package_type).packages; - if stored_packages.len() != packages.len() { - self.get_mut(package_type).packages = packages; - self.sync_to_file_by_type(dirs, package_type).await?; - } - - Ok(()) - } -} - -fn insert_package_by_path(packages: &mut PackagesMap, path: PackagePath, mut package: Package) { - if let Ok(file_name) = path.file_name() { - let is_disabled = file_name.ends_with(".disabled"); - - if is_disabled { - package.disabled = true; - } - } - - packages.insert(path, package); -} - -#[tracing::instrument(skip(packages, to_infer))] -async fn infer_provider( - ingress: &IngressId, - provider: Providers, - package_type: PackageType, - packages: &mut PackagesMap, - to_infer: HashSet, - hash_fn: fn(&PathBuf) -> crate::Result, -) -> Option> { - let _ = send_ingress(ingress, 5.0, Some(format!("inferring {package_type}s from {provider}").as_str())).await; - - let mut hash_map = HashMap::::new(); - for path in &to_infer { - if let Ok(hash) = hash_fn(path) { - hash_map.insert(hash, path.clone()); - } - } - - - let hash_len = hash_map.len(); - - let _ = send_ingress(ingress, 25.0, None).await; - match provider.get_versions_by_hashes(hash_map.iter().map(|hash| hash.0.clone()).collect()).await { - Ok(versions) => { - - let found_all = (hash_len - versions.len()) == 0; - - let havent_found = if found_all { - None - } else { - Some(hash_map.iter().filter_map(|(hash, path)| { - if versions.contains_key(hash) { - None - } else { - Some(path.clone()) - } - }).collect::>()) - }; - - let package_ids = versions.iter().map(|(hash, version)| (version.package_id.clone(), hash.clone())).collect::>(); - - let _ = send_ingress(ingress, 25.0, None).await; - match provider.get_multiple(package_ids.clone().into_keys().collect::>().as_slice()).await { - Ok(managed_packages) => { - for managed in managed_packages { - let Some(hash) = package_ids.get(&managed.id) else { continue }; - - let Some(version) = versions.get(hash) else { continue; }; - - if let Some(path) = hash_map.get(hash) { - let meta = PackageMetadata::Managed { - package_id: managed.id, - provider: provider.clone(), - package_type, - title: managed.title, - version_id: version.id.clone(), - version_formatted: version.version_display.clone(), - mc_versions: Some(version.game_versions.clone()), - icon_url: managed.icon_url - }; - - let Ok(package_path) = PackagePath::from_path(path).await else { - tracing::error!("failed to create package path for {:?}", path); - continue; - }; - - match Package::new(path, meta) { - Ok(package) => { - insert_package_by_path(packages, package_path, package); - }, - Err(err) => { - tracing::error!("failed to create package from {}: {}", provider, err); - }, - }; - } - } - }, - Err(err) => { - tracing::error!("failed to get versions from {}: {}", provider, err); - } - } - - let _ = send_ingress(ingress, 45.0, Some(format!("infered {package_type}s from {provider}").as_str())).await; - return havent_found; - }, - Err(err) => { - tracing::error!("failed to get versions hashes from {}: {}", provider, err); - } - }; - - let _ = send_ingress(ingress, 70.0, Some(format!("failed to infer {package_type}s from {provider}").as_str())).await; - Some(to_infer) -} - -/// Metadata that represents a [`Package`]. -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Deserialize, Clone, Debug)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum PackageMetadata { - Managed { - package_id: String, - provider: Providers, - package_type: PackageType, - title: String, - version_id: String, - version_formatted: String, - #[serde(skip_serializing_if = "Option::is_none", default)] - mc_versions: Option>, - #[serde(skip_serializing_if = "Option::is_none", default)] - icon_url: Option, - }, - Unknown, -} - -impl PackageMetadata { - #[must_use] - pub fn from_managed_package(package: ManagedPackage, version: ManagedVersion) -> Self { - Self::Managed { - package_id: package.id, - version_id: version.id, - version_formatted: version.version_display, - title: package.title, - provider: package.provider, - package_type: package.package_type, - mc_versions: Some(version.game_versions), - icon_url: package.icon_url, - } - } - - #[must_use] - pub const fn get_package_type(&self) -> Option { - match self { - Self::Managed { package_type, .. } => Some(*package_type), - Self::Unknown => None, - } - } -} - -/// Universal metadata for any managed user from a Mod distribution platform. -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct ManagedUser { - pub id: String, - pub username: String, - #[serde(default)] - pub url: Option, - #[serde(default)] - pub avatar_url: Option, - #[serde(default)] - pub bio: Option, - #[serde(default = "default_is_organization_user")] - pub is_organization_user: bool, - #[serde(default)] - pub role: Option, -} - -const fn default_is_organization_user() -> bool { - false -} - -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Deserialize, Clone, Debug)] -pub enum Author { - Team { - team: String, - organization: Option, - }, - Users(Vec), -} - -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Deserialize, Clone, Debug)] -pub enum PackageBody { - Url(String), - Markdown(String), -} - -/// Universal metadata for any managed package from a Mod distribution platform. -#[serde_with::serde_as] -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct ManagedPackage { - // Core Metadata - pub provider: Providers, - pub id: String, - pub package_type: PackageType, - pub title: String, - pub description: String, - pub body: PackageBody, - pub main: String, - pub versions: Vec, - pub game_versions: Vec, - #[serde_as(as = "serde_with::VecSkipError<_>")] - pub loaders: Vec, - pub icon_url: Option, - - pub created: Option>, - pub updated: Option>, - pub client: PackageSide, - pub server: PackageSide, - pub downloads: u64, - pub followers: u32, - pub categories: Vec, - pub optional_categories: Option>, - pub license: Option, - pub author: Author, - - pub is_archived: bool, -} - -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct License { - #[serde(default)] - pub id: String, - #[serde(default)] - pub name: String, - #[serde(default)] - pub url: Option, -} - -/// Universal managed package version of a package. -#[serde_with::serde_as] -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct ManagedVersion { - pub id: String, - pub package_id: String, - pub author: String, - pub name: String, - - pub featured: bool, - pub version_display: String, - pub changelog: String, - pub changelog_url: Option, - - pub published: Option>, - pub downloads: u32, - pub version_type: ManagedVersionReleaseType, - - pub files: Vec, - pub is_available: bool, - pub deps: Vec, - pub game_versions: Vec, - #[serde_as(as = "serde_with::VecSkipError<_>")] - pub loaders: Vec, -} - -impl ManagedVersion { - #[must_use] - pub fn get_primary_file(&self) -> Option<&ManagedVersionFile> { - self.files.iter().find(|f| f.primary) - } -} - -/// Universal version release type -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Default, Serialize, Deserialize, Clone, Debug)] -pub enum ManagedVersionReleaseType { - #[default] - Release, - Alpha, - Beta, - Snapshot, -} - -impl From for ManagedVersionReleaseType { - fn from(s: String) -> Self { - match s.to_lowercase().as_str() { - "alpha" => Self::Alpha, - "beta" => Self::Beta, - "snapshot" => Self::Snapshot, - _ => Self::Release, - } - } -} - -impl Display for ManagedVersionReleaseType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { - Self::Release => "Release", - Self::Alpha => "Alpha", - Self::Beta => "Beta", - Self::Snapshot => "Snapshot", - }) - } -} - -/// Universal interface for managed package files. -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] -pub struct ManagedVersionFile { - pub url: String, - pub file_name: String, - pub primary: bool, - - pub size: u64, - pub file_type: Option, - pub hashes: HashMap, -} - -/// Universal interface for managed package dependencies. -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct ManagedDependency { - pub version_id: Option, - pub package_id: Option, - pub file_name: Option, - pub dependency_type: PackageDependency, -} - -/// The type of a [`ManagedDependency`]. -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Deserialize, Clone, Debug)] -pub enum PackageDependency { - Required, - Optional, - Incompatible, - Embedded, -} - -/// The Client/Server side type of a [`Package`]. -#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] -#[serde(rename_all = "snake_case")] -#[cfg_attr(feature = "specta", derive(specta::Type))] -pub enum PackageSide { - Required, - Optional, - Unsupported, - Unknown, -} - -/// The file type of a [`Package`]. -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] -#[serde(rename_all = "snake_case")] -pub enum PackageFile { - RequiredPack, - OptionalPack, - Unknown, -} - -#[tracing::instrument(skip(io_semaphore))] -#[onelauncher_macros::memory] -pub async fn read_icon( - icon_path: Option, - cache_dir: &Path, - path: &PathBuf, - io_semaphore: &IoSemaphore, -) -> crate::Result> { - if let Some(icon_path) = icon_path { - let zip_reader = ZipFileReader::new(path).await; - if let Ok(zip_reader) = zip_reader { - let zip_idx_res = zip_reader - .file() - .entries() - .iter() - .position(|f| f.filename().as_str().unwrap_or_default() == icon_path); - - if let Some(zip_idx) = zip_idx_res { - let mut bytes = Vec::new(); - if zip_reader - .reader_with_entry(zip_idx) - .await? - .read_to_end_checked(&mut bytes) - .await - .is_ok() - { - let bytes = bytes::Bytes::from(bytes); - let path = write_icon(&icon_path, cache_dir, bytes, io_semaphore).await?; - - return Ok(Some(path)); - } - } - } - } - - Ok(None) -} diff --git a/packages/core_old/src/store/processor.rs b/packages/core_old/src/store/processor.rs deleted file mode 100644 index ff572f32..00000000 --- a/packages/core_old/src/store/processor.rs +++ /dev/null @@ -1,762 +0,0 @@ -//! Handles child processes in a unified API - -use chrono::{DateTime, TimeZone, Utc}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::process::Stdio; -use std::sync::Arc; -use tokio::io::{AsyncBufReadExt, BufReader}; -use tokio::process::{Child, Command}; -use tokio::sync::RwLock; -use tokio::task::JoinHandle; -use uuid::Uuid; - -use crate::api::cluster; -use crate::constants::PROCESSOR_FILE; -use crate::proxy::send::{send_message, send_process}; -use crate::proxy::ProcessPayloadType; -use crate::utils::http::read_json; -use crate::State; -use onelauncher_utils::io::IOError; - -use super::{Cluster, ClusterPath}; - -/// Wrapper over a `HashMap` of PIDs to `ProcessorChildren` and unified apis -pub struct Processor(HashMap>>); - -/// Manager for safe processes like ingress feeds which need to be safely shut down. -pub struct IngressProcessor { - /// A vector of ingress feed uuids. - pub ingress_feeds: Vec, -} - -/// A type of Ingress safety process. -#[derive(Debug, Copy, Clone)] -pub enum IngressProcessType { - /// A single Ingress feed. - IngressFeed, - // Writes, - // Reads, - // Fetch, -} - -impl Default for IngressProcessor { - fn default() -> Self { - Self::new() - } -} - -impl IngressProcessor { - /// Initialize a new ingresss process manager. - #[must_use] - pub const fn new() -> Self { - Self { - ingress_feeds: Vec::new(), - } - } - - /// Add an ingress safety process to the `HashMap`. - pub async fn add_ingress(r#type: IngressProcessType, uuid: Uuid) -> crate::Result { - let state = State::get().await?; - let mut ingress_processor = state.ingress_processor.write().await; - match r#type { - IngressProcessType::IngressFeed => { - ingress_processor.ingress_feeds.push(uuid); - } - } - - Ok(uuid) - } - - /// Complete and retain an ingress safety process from the `HashMap` of ingress feeds. - pub async fn finish(r#type: IngressProcessType, uuid: Uuid) -> crate::Result<()> { - let state = State::get().await?; - let mut ingress_processor = state.ingress_processor.write().await; - match r#type { - IngressProcessType::IngressFeed => { - ingress_processor.ingress_feeds.retain(|f| *f != uuid); - } - } - Ok(()) - } - - /// Finish and check if an ingress safety processes ingress feed is empty and finished. - pub async fn finished(r#type: IngressProcessType) -> crate::Result { - let state = State::get().await?; - let ingress_processor = state.ingress_processor.read().await; - match r#type { - IngressProcessType::IngressFeed => { - if ingress_processor.ingress_feeds.is_empty() { - return Ok(true); - } - } - } - - Ok(false) - } -} - -/// A child process type. -#[derive(Debug)] -pub enum ChildType { - /// A tokio-managed child process. - ChildProcess(Child), - /// A restored child from a process cache that needs to be re-registered. - RescuedChild(u32), -} - -/// A structure representing a cache of a running process. -#[derive(Serialize, Deserialize, Debug)] -pub struct ProcessorCache { - /// The PID of a process. - pub pid: u32, - /// The stored UUID of a process. - pub uuid: Uuid, - /// The time a process started. - pub start_time: u64, - /// The name of a process. - pub name: String, - /// The executable file running the process. - pub exe: String, - /// The associated [`ClusterPath`] to a process. - pub cluster_path: ClusterPath, - /// A post hook to be run once a process has completed. - pub post: Option, - /// The [`Uuid`] of the Minecraft account it was started by. - #[serde(skip_serializing_if = "Option::is_none")] - pub user: Option, -} - -impl ChildType { - /// Get the PID of a specific `ChildType` process. - #[must_use] - pub fn id(&self) -> Option { - match self { - Self::ChildProcess(child) => child.id(), - Self::RescuedChild(pid) => Some(*pid), - } - } - - /// Store a cache of a specific `ChildType`. - pub async fn cache( - &self, - uuid: Uuid, - cluster_path: ClusterPath, - post: Option, - user: Option, - ) -> crate::Result<()> { - let pid = match self { - Self::ChildProcess(child) => child.id().unwrap_or(0), - Self::RescuedChild(pid) => *pid, - }; - - let state = State::get().await?; - let mut system = sysinfo::System::new(); - - system.refresh_processes(sysinfo::ProcessesToUpdate::All); - let process = system - .process(sysinfo::Pid::from_u32(pid)) - .ok_or_else(|| anyhow::anyhow!("could not find process {}", pid))?; - let start_time = process.start_time(); - let name = process.name().to_string_lossy().to_string(); - let Some(path) = process.exe() else { - return Err(anyhow::anyhow!("cached process {} has no path", pid).into()); - }; - - let exe = path.to_string_lossy().to_string(); - let cached_proccess = ProcessorCache { - pid, - uuid, - start_time, - name, - exe, - cluster_path, - post, - user, - }; - - let path = state.directories.caches_dir().await.join(PROCESSOR_FILE); - let mut caches = (read_json::>(&path, &state.io_semaphore) - .await) - .unwrap_or_default(); - - caches.insert(uuid, cached_proccess); - crate::utils::http::write(&path, &serde_json::to_vec(&caches)?, &state.io_semaphore) - .await?; - - Ok(()) - } - - /// Remove a child from the global store of processes. - pub async fn remove(&self, uuid: Uuid) -> crate::Result<()> { - let state = State::get().await?; - let path = state.directories.caches_dir().await.join(PROCESSOR_FILE); - let mut caches = (read_json::>(&path, &state.io_semaphore) - .await) - .unwrap_or_default(); - caches.remove(&uuid); - crate::utils::http::write(&path, &serde_json::to_vec(&caches)?, &state.io_semaphore) - .await?; - - Ok(()) - } - - /// Kill a process in the global store of processes. - pub async fn kill(&mut self) -> crate::Result<()> { - match self { - Self::ChildProcess(child) => Ok(child.kill().await.map_err(IOError::from)?), - Self::RescuedChild(pid) => { - let mut system = sysinfo::System::new(); - if system.refresh_processes(sysinfo::ProcessesToUpdate::Some(&[ - sysinfo::Pid::from_u32(*pid), - ])) != 0 - { - let process = system.process(sysinfo::Pid::from_u32(*pid)); - if let Some(process) = process { - process.kill(); - } - } - Ok(()) - } - } - } - - /// Wait for a process to complete. - pub fn try_wait(&mut self) -> crate::Result> { - match self { - Self::ChildProcess(child) => Ok(child - .try_wait() - .map_err(IOError::from)? - .map(|x| x.code().unwrap_or(0))), - Self::RescuedChild(pid) => { - let mut system = sysinfo::System::new(); - if system.refresh_processes(sysinfo::ProcessesToUpdate::Some(&[ - sysinfo::Pid::from_u32(*pid), - ])) == 0 - { - return Ok(Some(0)); - } - - system.process(sysinfo::Pid::from_u32(*pid)).map_or_else( - || Ok(Some(0)), - |process| { - if process.status() == sysinfo::ProcessStatus::Run { - Ok(None) - } else { - Ok(Some(0)) - } - }, - ) - } - } - } -} - -/// A [`Processor`] child process. -#[derive(Debug)] -pub struct ProcessorChild { - /// The uuid of a process. - pub uuid: Uuid, - /// The associated [`ClusterPath`] to a process. - pub cluster_path: ClusterPath, - /// The process manager managing this process. - pub manager: Option>>, - /// The [`ChildType`] of this process. - pub current_child: Arc>, - /// When this process was last updated in [`Utc`]. - pub last_updated: DateTime, - /// When this process was last updated in [`Utc`]. - pub started_at: DateTime, - /// What [`Uuid`] this process is running under. - pub user: Option, -} - -impl Processor { - /// Get a new [`Processor`] instance. - #[must_use] - pub fn new() -> Self { - Self(HashMap::new()) - } - - /// Returns a reference to the value corresponding to the key. - #[must_use] - pub fn get(&self, uuid: Uuid) -> Option>> { - self.0.get(&uuid).cloned() - } - - /// An collection visiting all keys in arbitrary order. - #[must_use] - pub fn keys(&self) -> Vec { - self.0.keys().copied().collect() - } - - /// Get all running processes. - pub async fn running(&self) -> crate::Result> { - let mut keys = Vec::new(); - for key in self.keys() { - if let Some(process) = self.get(key) { - let process = process.clone(); - let process = process.write().await; - if process.current_child.write().await.try_wait()?.is_none() { - keys.push(key); - } - } - } - Ok(keys) - } - - /// Get all running cluster processes. - pub async fn running_cluster(&self, cluster_path: ClusterPath) -> crate::Result> { - let running = self.running().await?; - let mut keys = Vec::new(); - for key in running { - if let Some(process) = self.get(key) { - let process = process.clone(); - let process = process.read().await; - if process.cluster_path == cluster_path { - keys.push(key); - } - } - } - Ok(keys) - } - - /// get all running cluster paths. - pub async fn running_cluster_paths(&self) -> crate::Result> { - let mut clusters = Vec::new(); - for key in self.keys() { - if let Some(process) = self.get(key) { - let process = process.clone(); - let process = process.write().await; - if process.current_child.write().await.try_wait()?.is_none() { - clusters.push(process.cluster_path.clone()); - } - } - } - Ok(clusters) - } - - /// get all running clusters. - pub async fn running_clusters(&self) -> crate::Result> { - let mut clusters = Vec::new(); - for key in self.keys() { - if let Some(process) = self.get(key) { - let process = process.clone(); - let process = process.write().await; - if process.current_child.write().await.try_wait()?.is_none() { - if let Some(cluster) = cluster::get(&process.cluster_path.clone()).await? { - clusters.push(cluster); - } - } - } - } - Ok(clusters) - } - - /// get the exit status of a process by its [`Uuid`]. - pub async fn exit_status(&self, uuid: Uuid) -> crate::Result> { - if let Some(process) = self.get(uuid) { - let process = process.write().await; - let status = process.current_child.write().await.try_wait()?; - Ok(status) - } else { - Ok(None) - } - } - - /// restore processes from the cache. - pub async fn restore(&mut self) -> crate::Result<()> { - let state = State::get().await?; - let processor_path = state.directories.caches_dir().await.join(PROCESSOR_FILE); - let mut processor_caches = if let Ok(processes_json) = - read_json::>(&processor_path, &state.io_semaphore).await - { - let zeros = HashMap::::new(); - crate::utils::http::write( - &processor_path, - &serde_json::to_vec(&zeros)?, - &state.io_semaphore, - ) - .await?; - processes_json - } else { - HashMap::new() - }; - - for (_, cache) in processor_caches.drain() { - let uuid = cache.uuid; - match self.insert_restored(cache).await { - Ok(process) => { - self.0.insert(uuid, process); - } - Err(e) => tracing::warn!("failed to restore process {}: {}", uuid, e), - } - } - - Ok(()) - } - - /// insert a process with a command and [`ClusterPath`]. - #[tracing::instrument(skip(self, uuid, command, post, censors))] - #[tracing::instrument(level = "trace", skip(self))] - #[onelauncher_macros::memory] - #[allow(clippy::too_many_arguments)] - pub async fn insert_process( - &mut self, - uuid: Uuid, - cluster_path: ClusterPath, - mut command: Command, - post: Option, - censors: HashMap, - user: Option, - enable_gamemode: Option, - ) -> crate::Result>> { - command - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .stdin(Stdio::null()); - - let mut proc = command.spawn().map_err(IOError::from)?; - - let pid = proc - .id() - .ok_or_else(|| anyhow::anyhow!("process failed, couldn't get pid"))?; - - let stdout = proc.stdout.take().unwrap(); - let stderr = proc.stderr.take().unwrap(); - - #[cfg(target_os = "linux")] - { - if enable_gamemode.is_some_and(|x| x) { - if let Err(err) = onelauncher_gamemode::request_start_for_wrapper(pid) { - tracing::warn!("failed to enable gamemode, continuing: {}", err); - }; - } - } - - tokio::spawn(async move { - let mut stdout = BufReader::new(stdout).lines(); - let mut stderr = BufReader::new(stderr).lines(); - - while let Ok(Some(line)) = tokio::select! { - line = stdout.next_line() => line, - line = stderr.next_line() => line, - } { - let mut censored = line.clone(); - for (key, value) in &censors { - censored = censored.replace(key, value); - } - - if let Err(err) = - send_process(uuid, pid, ProcessPayloadType::Logging, &censored).await - { - tracing::warn!("failed to send process log: {}", err); - }; - } - }); - - let process = ChildType::ChildProcess(proc); - - process - .cache(uuid, cluster_path.clone(), post.clone(), user) - .await?; - - let current_child = Arc::new(RwLock::new(process)); - let manager = Some(tokio::spawn(Self::manager( - uuid, - post, - pid, - current_child.clone(), - cluster_path.clone(), - ))); - - send_process(uuid, pid, ProcessPayloadType::Started, "started process").await?; - let last_updated = Utc::now(); - let child = ProcessorChild { - uuid, - cluster_path, - current_child, - manager, - last_updated, - started_at: Utc::now(), - user, - }; - let child = Arc::new(RwLock::new(child)); - self.0.insert(uuid, child.clone()); - Ok(child) - } - - /// Insert a cached process with a [`ProcessorCache`]. - #[tracing::instrument(skip(self, cache))] - #[tracing::instrument(level = "trace", skip(self))] - #[onelauncher_macros::memory] - pub async fn insert_restored( - &mut self, - cache: ProcessorCache, - ) -> crate::Result>> { - let _state = State::get().await?; - - // ensure located stray process matches with our pid recorded process - { - let mut system = sysinfo::System::new(); - system.refresh_processes(sysinfo::ProcessesToUpdate::All); - let process = system - .process(sysinfo::Pid::from_u32(cache.pid)) - .ok_or_else(|| anyhow::anyhow!("could not find pid {}", cache.pid))?; - - if cache.start_time != process.start_time() { - return Err(anyhow::anyhow!( - "restored process {} has a mismatched start time {}", - cache.pid, - process.start_time() - ) - .into()); - } - if cache.name != process.name().to_string_lossy() { - return Err(anyhow::anyhow!( - "restored process {} has a mismatched name {}", - cache.pid, - process.name().to_string_lossy().to_string() - ) - .into()); - } - - if let Some(path) = process.exe() { - if cache.exe != path.to_string_lossy() { - return Err(anyhow::anyhow!( - "restored process {} has a mismatched exe {}", - cache.pid, - path.to_string_lossy() - ) - .into()); - } - } else { - return Err( - anyhow::anyhow!("restored process {} has no exe path", cache.pid).into(), - ); - } - } - - let process = ChildType::RescuedChild(cache.pid); - let pid = process - .id() - .ok_or_else(|| anyhow::anyhow!("process failed, couldnt get pid"))?; - process - .cache( - cache.uuid, - cache.cluster_path.clone(), - cache.post.clone(), - cache.user, - ) - .await?; - let current_child = Arc::new(RwLock::new(process)); - let manager = Some(tokio::spawn(Self::manager( - cache.uuid, - cache.post, - pid, - current_child.clone(), - cache.cluster_path.clone(), - ))); - - send_process( - cache.uuid, - pid, - ProcessPayloadType::Started, - "started process", - ) - .await?; - - let last_updated = Utc::now(); - let child = ProcessorChild { - uuid: cache.uuid, - cluster_path: cache.cluster_path, - started_at: Utc - .timestamp_opt(cache.start_time as i64, 0) - .single() - .ok_or_else(|| { - anyhow::anyhow!("couldn't convert processor cache timestamp to Utc") - })?, // TODO: Look into this - current_child, - manager, - last_updated, - user: cache.user, - }; - - let child = Arc::new(RwLock::new(child)); - self.0.insert(cache.uuid, child.clone()); - Ok(child) - } - - /// Get a process manager and runner awaiting for the exit status. - #[tracing::instrument(skip(current_child))] - #[onelauncher_macros::memory] - async fn manager( - uuid: Uuid, - post: Option, - mut current_pid: u32, - current_child: Arc>, - cluster_path: ClusterPath, - ) -> crate::Result { - let current_child = current_child.clone(); - let mut exit_status; - let mut last_updated = Utc::now(); - - // core main process loop, managed by tokio - loop { - let value = current_child.write().await.try_wait(); - if let Some(stat) = value? { - exit_status = stat; - break; - } - - tokio::time::sleep(tokio::time::Duration::from_millis(50)).await; - let update = Utc::now().signed_duration_since(last_updated).num_seconds(); - if update >= 60 { - if let Err(err) = cluster::edit(&cluster_path, |cluster| { - cluster.meta.recently_played += update as u64; - async { Ok(()) } - }) - .await - { - tracing::warn!( - "failed to update playtime for cluster {}: {}", - &cluster_path, - err - ); - } - last_updated = Utc::now(); - } - } - - let update = Utc::now().signed_duration_since(last_updated).num_seconds(); - if let Err(err) = cluster::edit(&cluster_path, |cluster| { - cluster.meta.recently_played += update as u64; - async { Ok(()) } - }) - .await - { - tracing::warn!( - "failed to update playtime for cluster {}: {}", - &cluster_path, - err - ); - } - - let cluster_path_in = cluster_path.clone(); - tokio::spawn(async move { - if let Err(err) = cluster::update_playtime(&cluster_path_in).await { - tracing::warn!( - "failed to update playtime for cluster {}: {}", - &cluster_path_in, - err - ); - } - }); - - tokio::spawn(async { - let state = State::get().await; - let state = match state { - Ok(state) => state, - Err(err) => { - tracing::warn!("failed to get state: {}", err); - return; - } - }; - let _ = state.discord_rpc.clear(true).await; - }); - - #[cfg(feature = "tauri")] - { - let window = crate::ProxyState::get_main_window().await?; - window.unminimize()?; - } - - { - let current_child = current_child.write().await; - current_child.remove(uuid).await?; - } - - if !exit_status == 0 { - send_process( - uuid, - current_pid, - ProcessPayloadType::Finished, - "exited process", - ) - .await?; - - return Ok(exit_status); - } - - let post = if let Some(hook) = post { - if hook.is_empty() { - None - } else { - let mut cmd = hook.split(' '); - if let Some(c) = cmd.next() { - let mut c = Command::new(c); - c.args(cmd).current_dir(cluster_path.full_path().await?); - Some(c) - } else { - None - } - } - } else { - None - }; - - if let Some(mut m_c) = post { - let result = { - { - let mut current_child: tokio::sync::RwLockWriteGuard<'_, ChildType> = - current_child.write().await; - - let new_child = m_c.spawn().map_err(IOError::from)?; - - current_pid = new_child - .id() - .ok_or_else(|| anyhow::anyhow!("process failed, couldnt get pid"))?; - - *current_child = ChildType::ChildProcess(new_child); - }; - - send_process( - uuid, - current_pid, - ProcessPayloadType::Modified, - "running post hook", - ) - .await?; - - let value = current_child.write().await.try_wait(); - - if let Ok(Some(stat)) = value { - exit_status = stat; - } - - Ok::<(), crate::Error>(()) - }; - - if let Err(err) = result { - tracing::error!("failed to run post hook: {err}"); - let _ = send_message(format!("failed to run post hook: {err}").as_str()).await; - } - }; - - send_process( - uuid, - current_pid, - ProcessPayloadType::Finished, - "exited process", - ) - .await?; - - Ok(exit_status) - } -} - -impl Default for Processor { - fn default() -> Self { - Self::new() - } -} diff --git a/packages/core_old/src/store/settings.rs b/packages/core_old/src/store/settings.rs deleted file mode 100644 index c51d0a65..00000000 --- a/packages/core_old/src/store/settings.rs +++ /dev/null @@ -1,209 +0,0 @@ -//! Handles user-inputted settings and global values - -use super::{Directories, JavaVersions}; -use crate::constants::CURRENT_SETTINGS_FORMAT_VERSION; -use onelauncher_utils::io; -use serde::{Deserialize, Serialize}; -use std::path::{Path, PathBuf}; - -/// A global settings state for the launcher. -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct Settings { - /// A `OneLauncher` theme managed by the `OneLauncher` GUI. - #[serde(default = "theme_default")] - pub theme: String, - /// A global browser list view for the `OneLauncher` GUI. - #[serde(default)] - pub browser_list_view: BrowserListView, - /// Does not ask for confirmation when closing the `OneLauncher` GUI - #[serde(default)] - pub hide_close_prompt: bool, - /// Disables animations in the `OneLauncher` GUI - #[serde(default)] - pub disable_animations: bool, - /// A global fullscreen Minecraft state. - #[serde(default)] - pub force_fullscreen: bool, - /// Whether to allow launching the same cluster under the same account. - #[serde(default)] - pub allow_parallel_running_clusters: bool, - /// Whether to launch Feral Gamemode on Linux systems. - #[serde(default)] - pub enable_gamemode: bool, - /// A global default [`Resolution`] for Minecraft. - pub resolution: Resolution, - /// A global [`JavaVersions`] list and default version. - pub java_versions: JavaVersions, - /// A global [`Memory`] settings store for Java memory settings. - pub memory: Memory, - /// Global and default initialization hooks . - pub init_hooks: InitHooks, - /// Global and default custom Java arguments. - pub custom_java_args: Vec, - /// Global and default custom environment variables. - pub custom_env_args: Vec<(String, String)>, - /// Global and default maximum [`Semaphore`] I/O operations. - pub max_async_io_operations: usize, - /// Global and default maximum [`Semaphore`] HTTP operations. - pub max_async_fetches: usize, - /// The [`CURRENT_FORMAT_VERSION`] of this settings file. - pub version: u32, - /// Whether or not to disable Discord IPC. - #[serde(default)] - pub disable_discord: bool, - /// Whether or not to enable a debug mode in the launcher. - #[serde(default)] - pub debug_mode: bool, - /// Whether or not to disable Plausible and Sentry analytics. - #[serde(default)] - pub disable_analytics: bool, - /// The core global config directory stored as a [`PathBuf`]. - #[serde(default = "Directories::init_settings_dir")] - pub config_dir: Option, - /// Whether or not to minimize the launcher upon a game launch. - #[serde(default)] - pub hide_on_launch: bool, - /// Enable/disable custom window decorations. - #[serde(default)] - pub custom_frame: bool, - /// Completed onboarding. - #[serde(default)] - pub onboarding_completed: bool, -} - -fn theme_default() -> String { - "dark".to_string() -} - -impl Settings { - /// Initializes the global settings state. - #[tracing::instrument] - pub async fn initialize(file: &Path) -> crate::Result { - let mut recovered_corruption = false; - - let settings = if file.exists() { - let read_settings = io::read(&file) - .await - .map_err(|err| anyhow::anyhow!("error reading settings file: {0}", err).into()) - .and_then(|it| serde_json::from_slice::(&it).map_err(crate::Error::from)); - - if let Err(ref err) = read_settings { - tracing::error!("failed to load settings file: {err}."); - let backup = file.with_extension("json.bak"); - tracing::error!( - "corrupted settings will be backed up as {}, and a new one will be created", - backup.display() - ); - let _ = io::rename(file, backup).await; - recovered_corruption = true; - } - - read_settings.ok() - } else { - None - }; - - if let Some(settings) = settings { - Ok(settings) - } else { - let settings = Self { - theme: "dark".to_string(), - browser_list_view: BrowserListView::Grid, - hide_close_prompt: true, - disable_animations: false, - force_fullscreen: false, - allow_parallel_running_clusters: false, - #[cfg(target_os = "linux")] - enable_gamemode: true, - #[cfg(not(target_os = "linux"))] - enable_gamemode: false, - resolution: Resolution::default(), - java_versions: JavaVersions::new(), - memory: Memory::default(), - init_hooks: InitHooks::default(), - custom_env_args: Vec::new(), - custom_java_args: Vec::new(), - max_async_fetches: 10, - max_async_io_operations: 10, - version: CURRENT_SETTINGS_FORMAT_VERSION, - disable_analytics: false, - disable_discord: false, - debug_mode: false, - config_dir: Directories::init_settings_dir(), - hide_on_launch: false, - custom_frame: true, - onboarding_completed: false, - }; - - if recovered_corruption { - settings.sync(file).await?; - } - - Ok(settings) - } - } - - /// Synchronizes the current settings from a file. - #[tracing::instrument(skip(self))] - pub async fn sync(&self, to: &Path) -> crate::Result<()> { - io::write(to, serde_json::to_vec(self)?).await?; - Ok(()) - } -} - -/// `OneLauncher` browser list type. -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum BrowserListView { - #[default] - Grid, - List, -} - -/// Global memory settings across all clusters. -#[derive(Serialize, Deserialize, Debug, Clone, Copy)] -#[cfg_attr(feature = "specta", derive(specta::Type))] -pub struct Memory { - /// Maximum amount of Java memory available globally. - pub maximum: u32, - /// Minimum amount of Java memory available globally. - pub minimum: u32, -} - -impl Default for Memory { - fn default() -> Self { - Self { - maximum: 2048, - minimum: 256, - } - } -} - -/// Global Minecraft resolution. -#[derive(Serialize, Deserialize, Debug, Clone, Copy)] -#[cfg_attr(feature = "specta", derive(specta::Type))] -pub struct Resolution(pub u16, pub u16); - -impl Default for Resolution { - fn default() -> Self { - Self(854, 480) - } -} - -/// Global initialization hooks for all Minecraft clusters. -#[derive(Serialize, Deserialize, Debug, Clone, Default)] -#[serde(default)] -#[cfg_attr(feature = "specta", derive(specta::Type))] -pub struct InitHooks { - /// Pre-launch hook. - #[serde(skip_serializing_if = "Option::is_none")] - pub pre: Option, - /// Wrapper hook for the runtime of the game instance. - #[serde(skip_serializing_if = "Option::is_none")] - pub wrapper: Option, - /// Post-launch hook. - #[serde(skip_serializing_if = "Option::is_none")] - pub post: Option, -} diff --git a/packages/core_old/src/utils/crypto.rs b/packages/core_old/src/utils/crypto.rs deleted file mode 100644 index a33e9082..00000000 --- a/packages/core_old/src/utils/crypto.rs +++ /dev/null @@ -1,68 +0,0 @@ -use std::path::PathBuf; - -use crate::Result; - -pub fn sha1(bytes: &[u8]) -> String { - sha1_smol::Sha1::from(bytes).digest().to_string() -} - -pub fn sha1_file(file: &PathBuf) -> Result { - Ok(sha1(&std::fs::read(file)?)) -} - -pub fn sha1_files(files: Vec<&PathBuf>) -> Vec> { - let mut hashes = Vec::new(); - for file in files { - hashes.push(sha1_file(file).ok()); - } - hashes -} - -pub fn md5(bytes: &[u8]) -> String { - let digest = md5::compute(bytes); - format!("{:x}", digest) -} - -pub fn md5_file(file: &PathBuf) -> Result { - Ok(md5(&std::fs::read(file)?)) -} - -pub fn md5_files(files: Vec<&PathBuf>) -> Vec> { - let mut hashes = Vec::new(); - for file in files { - hashes.push(md5_file(file).ok()); - } - hashes -} - -const CURSEFORGE_FINGERPRINT_SEED: u32 = 1; -pub fn murmur2(bytes: &[u8]) -> u32 { - let normalized = normalize_byte_array(bytes); - murmur2::murmur2(normalized.as_slice(), CURSEFORGE_FINGERPRINT_SEED) -} - -pub fn murmur2_file(file: &PathBuf) -> Result { - Ok(murmur2(&std::fs::read(file)?)) -} - -pub fn murmur2_files(files: Vec<&PathBuf>) -> Vec> { - let mut hashes = Vec::new(); - for file in files { - hashes.push(murmur2_file(file).ok()); - } - hashes -} - -// Curseforge and their documented code :D (it wasn't documented) -// https://github.com/CurseForgeCommunity/.NET-APIClient/blob/2c4f5f613d20025f9286fdd53592f8519022918f/Murmur2.cs#L8-L21 -// https://github.com/comp500/infra.link/blob/a298502fdb15b8939fe0effe71ed42ad06f3946f/murmur2/go/main.go#L10-L40 -fn normalize_byte_array(bytes: &[u8]) -> Vec { - let normalized = bytes - .iter() - .filter(|byte| !matches!(byte, 9 | 10 | 13 | 32)) - .copied() - .collect::>(); - // println!("Old len {}", bytes.len()); - // println!("New len {}", normalized.len()); - normalized -} diff --git a/packages/core_old/src/utils/http.rs b/packages/core_old/src/utils/http.rs deleted file mode 100644 index 280c4aa6..00000000 --- a/packages/core_old/src/utils/http.rs +++ /dev/null @@ -1,303 +0,0 @@ -//! **HTTP Utilities** -//! -//! Async extensions and wrappers around [`reqwest`] functions. - -use std::collections::HashMap; -use std::ffi::OsStr; -use std::path::{Path, PathBuf}; - -use bytes::Bytes; -use reqwest::Method; -use serde::de::DeserializeOwned; -use tokio::io::AsyncWriteExt; -use tokio::sync::{RwLock, Semaphore}; - -use crate::constants::FETCH_ATTEMPTS; -use crate::proxy::send::send_ingress; -use crate::proxy::IngressId; - -use onelauncher_utils::io; -use onelauncher_utils::io::IOError; - -use super::crypto; - -/// A [`Semaphore`] used for all I/O operations. -#[derive(Debug)] -pub struct IoSemaphore(pub RwLock); -/// A [`Semaphore`] used for all HTTP operations. -#[derive(Debug)] -pub struct FetchSemaphore(pub RwLock); - -lazy_static::lazy_static! { - /// A public reqwest client with configured headers. - pub static ref REQWEST_CLIENT: reqwest::Client = { - let mut headers = reqwest::header::HeaderMap::new(); - let header = reqwest::header::HeaderValue::from_str(&format!( - "{}/{} (https://polyfrost.org)", - crate::constants::NAME, - crate::constants::VERSION, - )).expect("failed to build reqwest headers!"); - headers.insert(reqwest::header::USER_AGENT, header); - reqwest::Client::builder() - .tcp_keepalive(Some(std::time::Duration::from_secs(15))) - .default_headers(headers) - .build() - .expect("failed to build reqwest client!") - }; -} - -/// Basic HTTP fetch interface. -#[tracing::instrument(skip(semaphore))] -pub async fn fetch( - url: &str, - sha1: Option<&str>, - semaphore: &FetchSemaphore, -) -> crate::Result { - fetch_advanced(Method::GET, url, sha1, None, None, None, semaphore).await -} - -/// Basic JSON fetch interface. -#[tracing::instrument(skip(json_body, semaphore))] -pub async fn fetch_json( - method: Method, - url: &str, - sha1: Option<&str>, - json_body: Option, - semaphore: &FetchSemaphore, -) -> crate::Result -where - T: DeserializeOwned, -{ - let result = fetch_advanced(method, url, sha1, json_body, None, None, semaphore).await?; - - let value = serde_json::from_slice(&result)?; - Ok(value) -} - -/// Advanced HTTP fetch interface with json, sha1 validation, and ingress support. -#[tracing::instrument(skip(json_body, semaphore))] -#[onelauncher_macros::memory] -pub async fn fetch_advanced( - method: Method, - url: &str, - sha1: Option<&str>, - json_body: Option, - headers: Option>, - ingress: Option<(&IngressId, f64)>, - semaphore: &FetchSemaphore, -) -> crate::Result { - let io_semaphore = semaphore.0.read().await; - let _permit = io_semaphore.acquire().await?; - - for attempt in 1..=(FETCH_ATTEMPTS + 1) { - let mut req = REQWEST_CLIENT.request(method.clone(), url); - - if let Some(body) = json_body.clone() { - req = req.json(&body); - } - - if let Some(headers) = &headers { - for (key, value) in headers { - req = req.header(*key, *value); - } - } - - let result = req.send().await; - - match result { - Ok(x) => { - let bytes = if let Some((feed, total)) = &ingress { - let length = x.content_length(); - if let Some(total_size) = length { - use futures::StreamExt; - let mut stream = x.bytes_stream(); - let mut bytes = Vec::new(); - while let Some(item) = stream.next().await { - let chunk = - item.or(Err(anyhow::anyhow!("no value for fetch bytes")))?; - bytes.append(&mut chunk.to_vec()); - send_ingress( - feed, - (chunk.len() as f64 / total_size as f64) * total, - None, - ) - .await?; - } - - Ok(Bytes::from(bytes)) - } else { - x.bytes().await - } - } else { - x.bytes().await - }; - - if let Ok(bytes) = bytes { - if let Some(sha1) = sha1 { - let hash = crypto::sha1(&bytes); - if &*hash != sha1 { - if attempt <= 3 { - continue; - } - return Err(anyhow::anyhow!( - "hash {0} does not match {1}", - sha1.to_string(), - hash - ) - .into()); - } - } - - tracing::trace!("finished downloading {url}"); - return Ok(bytes); - } else if attempt <= 3 { - continue; - } else if let Err(err) = bytes { - return Err(err.into()); - } - } - Err(_) if attempt <= 3 => continue, - Err(err) => { - return Err(err.into()); - } - } - } - - unreachable!() -} - -/// A utility to fetch from multiple mirrored sources. -#[tracing::instrument(skip(semaphore))] -#[onelauncher_macros::memory] -pub async fn fetch_from_mirrors( - mirrors: &[&str], - sha1: Option<&str>, - semaphore: &FetchSemaphore, -) -> crate::Result { - if mirrors.is_empty() { - return Err(anyhow::anyhow!("no mirrors provided!").into()); - } - - for (index, mirror) in mirrors.iter().enumerate() { - let result = fetch(mirror, sha1, semaphore).await; - if result.is_ok() || (result.is_err() && index == (mirrors.len() - 1)) { - return result; - } - } - - unreachable!() -} - -/// Checks if we are playing offline by contacting a reliable server. -#[tracing::instrument] -#[onelauncher_macros::memory] -pub async fn check_internet_connection(timeout: u64) -> bool { - REQWEST_CLIENT - .get("https://api.polyfrost.org/") - .timeout(std::time::Duration::from_secs(timeout)) - .send() - .await - .is_ok() -} - -/// Post JSON to a specified URL (helpful for Microsoft auth and Metadata) -#[tracing::instrument(skip(json_body, semaphore))] -#[onelauncher_macros::memory] -pub async fn post_json( - url: &str, - json_body: serde_json::Value, - semaphore: &FetchSemaphore, -) -> crate::Result -where - T: DeserializeOwned, -{ - let io_semaphore = semaphore.0.read().await; - let _permit = io_semaphore.acquire().await?; - - let req = REQWEST_CLIENT.post(url).json(&json_body); - - let result = req.send().await?.error_for_status()?; - let value = result.json().await?; - - Ok(value) -} - -/// Read JSON from a specified [`Path`]. -pub async fn read_json(path: &Path, semaphore: &IoSemaphore) -> crate::Result -where - T: DeserializeOwned, -{ - let io_semaphore = semaphore.0.read().await; - let _permit = io_semaphore.acquire().await?; - - let json = io::read(path).await?; - let json = serde_json::from_slice::(&json)?; - - Ok(json) -} - -/// Write to a file at a specified [`Path`]. -#[tracing::instrument(skip(bytes, semaphore))] -pub async fn write<'a>(path: &Path, bytes: &[u8], semaphore: &IoSemaphore) -> crate::Result<()> { - let io_semaphore = semaphore.0.read().await; - let _permit = io_semaphore.acquire().await?; - - if let Some(parent) = path.parent() { - io::create_dir_all(parent).await?; - } - - let mut file = tokio::fs::File::create(path) - .await - .map_err(|e| IOError::with_path(e, path))?; - file.write_all(bytes) - .await - .map_err(|e| IOError::with_path(e, path))?; - tracing::trace!("done writing to file {}", path.display()); - Ok(()) -} - -/// Copy a file from one [`AsRef`] to another. -pub async fn copy( - src: impl AsRef, - dest: impl AsRef, - semaphore: &IoSemaphore, -) -> crate::Result<()> { - let src = src.as_ref(); - let dest = dest.as_ref(); - - let io_semaphore = semaphore.0.read().await; - let _permit = io_semaphore.acquire().await?; - - if let Some(parent) = dest.parent() { - io::create_dir_all(parent).await?; - } - - io::copy(src, dest).await?; - tracing::trace!( - "done copying file from {} to {}", - src.display(), - dest.display() - ); - Ok(()) -} - -#[tracing::instrument(skip(bytes, io_semaphore))] -pub async fn write_icon( - icon_path: &str, - cache_dir: &Path, - bytes: Bytes, - io_semaphore: &IoSemaphore, -) -> crate::Result { - let ext = Path::new(&icon_path).extension().and_then(OsStr::to_str); - let hash = crypto::sha1(&bytes); - let path = cache_dir.join("icons").join(if let Some(e) = ext { - format!("{hash}.{e}") - } else { - hash - }); - - write(&path, &bytes, io_semaphore).await?; - - let path = io::canonicalize(path)?; - Ok(path) -} diff --git a/packages/core_old/src/utils/java.rs b/packages/core_old/src/utils/java.rs deleted file mode 100644 index 27ef9eba..00000000 --- a/packages/core_old/src/utils/java.rs +++ /dev/null @@ -1,366 +0,0 @@ -//! **Java Utilities** -//! -//! Async utilities for managing and downloading Java versions. - -use crate::constants::JAVA_BIN; -use crate::State; -use futures::prelude::*; -use serde::{Deserialize, Serialize}; -use std::collections::HashSet; -use std::env; -use std::path::{Path, PathBuf}; -use std::process::Command; -use tokio::task::JoinError; - -#[cfg(target_os = "windows")] -use winreg::{ - enums::{HKEY_LOCAL_MACHINE, KEY_READ, KEY_WOW64_32KEY, KEY_WOW64_64KEY}, - RegKey, -}; - -use onelauncher_utils::io; - -/// A struct representing a single version of the Java Runtime Environment. -/// Use [`locate_java`] to get an array of all located java instances on the machine. -/// -/// common java paths from under GPL 3.0 -/// -/// java check functionality from under MIT -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Clone)] -pub struct JavaVersion { - pub version: String, - pub arch: String, - pub path: String, -} - -/// Java entrypoint: Returns all possibly existing java paths on a Linux machine and validates them, -/// returning a [`Vec`] with all valid java installations, their paths, and associated metadata. -#[cfg(target_os = "windows")] -#[tracing::instrument] -pub async fn locate_java() -> Result, JavaError> { - let mut paths = HashSet::new(); - paths.extend(locate_java_path().await); - paths.extend(locate_installed_java().await?); - if let Ok(java_home) = env::var("JAVA_HOME") { - paths.insert(PathBuf::from(java_home)); - } - - let common_java_paths = [ - r"C:/Program Files/Java", - r"C:\Program Files\Eclipse Adoptium", - r"C:/Program Files (x86)/Java", - r"C:\Program Files (x86)/Eclipse Adoptium", - ]; - - for path in common_java_paths { - let Ok(java_subs) = std::fs::read_dir(path) else { - continue; - }; - for java_sub in java_subs.flatten() { - let full_path = java_sub.path(); - paths.insert(full_path.join("bin")); - } - } - - let common_key_paths = [ - r"SOFTWARE\\JavaSoft\\Java Runtime Environment", - r"SOFTWARE\\JavaSoft\\Java Development Kit", - r"SOFTWARE\\JavaSoft\\JRE", - r"SOFTWARE\\JavaSoft\\JDK", - r"SOFTWARE\\AdoptOpenJDK\\JRE", - r"SOFTWARE\\AdoptOpenJDK\\JDK", - r"SOFTWARE\\Eclipse Foundation\\JDK", - r"SOFTWARE\\Eclipse Foundation\\JRE", - r"SOFTWARE\\Eclipse Adoptium\\JRE", - r"SOFTWARE\\Eclipse Adoptium\\JDK", - r"SOFTWARE\\Microsoft\\JDK", - r"SOFTWARE\\Azul Systems\\Zulu", - r"SOFTWARE\\BellSoft\\Liberica", - ]; - - for key in common_key_paths { - if let Ok(java_key) = RegKey::predef(HKEY_LOCAL_MACHINE) - .open_subkey_with_flags(key, KEY_READ | KEY_WOW64_32KEY) - { - paths.extend(get_java_paths_from_windows(java_key)); - } - if let Ok(java_key) = RegKey::predef(HKEY_LOCAL_MACHINE) - .open_subkey_with_flags(key, KEY_READ | KEY_WOW64_64KEY) - { - paths.extend(get_java_paths_from_windows(java_key)); - } - } - - let java = check_java(paths).await.into_iter().collect(); - - Ok(java) -} - -/// Locates possibly existing java paths on Windows from Registry keys, returning as a [`HashSet`] -#[cfg(target_os = "windows")] -#[tracing::instrument] -pub fn get_java_paths_from_windows(java_key: RegKey) -> HashSet { - let mut paths = HashSet::new(); - - for subkey in java_key.enum_keys().flatten() { - if let Ok(subkey) = java_key.open_subkey(subkey) { - let common_subkey_value_names = [r"JavaHome", r"InstallationPath", r"\\hotspot\\MSI"]; - for subkey_value in common_subkey_value_names { - let path: Result = subkey.get_value(subkey_value); - let Ok(path) = path else { continue }; - paths.insert(PathBuf::from(path).join("bin")); - } - } - } - - paths -} - -/// Java entrypoint: Returns all possibly existing java paths on a macOS machine and validates them, -/// returning a [`Vec`] with all valid java installations, their paths, and associated metadata. -#[cfg(target_os = "macos")] -#[tracing::instrument] -pub async fn locate_java() -> Result, JavaError> { - let mut paths = HashSet::new(); - paths.extend(locate_java_path().await); - paths.extend(locate_installed_java().await?); - - let jvm_base = PathBuf::from("/Library/Java/JavaVirtualMachines/"); - if let Ok(dir) = std::fs::read_dir(jvm_base) { - for entry in dir.flatten() { - let entry = entry.path().join("Contents/Home/bin"); - paths.insert(entry); - } - } - - let common_java_paths = [ - r"/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands", - r"/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java", - r"/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home", - ]; - - for path in common_java_paths { - paths.insert(PathBuf::from(path)); - } - - let java = check_java(paths).await.into_iter().collect(); - - Ok(java) -} - -/// Java entrypoint: Returns all possibly existing java paths on a Linux machine and validates them, -/// returning a [`Vec`] with all valid java installations, their paths, and associated metadata. -#[cfg(target_os = "linux")] -#[tracing::instrument] -pub async fn locate_java() -> Result, JavaError> { - let mut paths = HashSet::new(); - paths.extend(locate_java_path().await); - paths.extend(locate_installed_java().await?); - - let common_java_paths = [ - r"/usr", - r"/usr/java", - r"/usr/lib/jvm", - r"/usr/lib64/jvm", - r"/usr/lib32/jvm", - r"/opt/jdk", - r"/opt/jdks", - r"/app/jdk", - ]; - - for path in common_java_paths { - let path = PathBuf::from(path); - paths.insert(PathBuf::from(&path).join("jre").join("bin")); - paths.insert(PathBuf::from(&path).join("bin")); - if let Ok(dir) = std::fs::read_dir(path) { - for entry in dir.flatten() { - let entry_path = entry.path(); - paths.insert(entry_path.join("jre").join("bin")); - paths.insert(entry_path.join("bin")); - } - } - } - - let java = check_java(paths).await.into_iter().collect(); - - Ok(java) -} - -/// Locate Java paths automatically added by the launcher instance. -#[tracing::instrument] -#[onelauncher_macros::memory] -async fn locate_installed_java() -> Result, JavaError> { - Box::pin(async move { - let mut paths = HashSet::new(); - let java_base_path = State::get() - .await - .map_err(|_| JavaError::MutexError)? - .directories - .java_dir() - .await; - - if java_base_path.is_dir() { - if let Ok(dir) = std::fs::read_dir(java_base_path) { - for entry in dir.flatten() { - let path = entry.path().join("bin"); - if let Ok(contents) = io::read_to_string(path.clone()).await { - let entry = entry.path().join(contents); - paths.insert(entry); - } else { - #[cfg(not(target_os = "macos"))] - { - let path = path.join(JAVA_BIN); - paths.insert(path); - } - } - } - } - } - - Ok(paths) - }) - .await -} - -/// Locates all installed Java instances from the `PATH` env variable, returning a -/// [`HashSet`] containing all paths in the `PATH` variable to be sorted into java versions. -#[tracing::instrument] -async fn locate_java_path() -> HashSet { - let paths = env::var("PATH").map(|p| env::split_paths(&p).collect::>()); - paths.unwrap_or_else(|_| HashSet::new()) -} - -/// Verifies that each Java instance is valid from a [`HashSet`], returning a -/// [`HashSet`] for each validated path containing the installation's metadata. -#[tracing::instrument] -pub async fn check_java(paths: HashSet) -> HashSet { - let java_instances = stream::iter(paths.into_iter()) - .map(|p: PathBuf| tokio::task::spawn(async move { check_java_instance(&p).await })) - .buffer_unordered(64) - .collect::>() - .await; - - java_instances - .into_iter() - .filter_map(Result::ok) - .flatten() - .collect() -} - -/// Verifies that a java instance [`Path`] is valid, returning a [`JavaVersion`] if the java path -/// is valid, containing the installation's metadata. -/// java_check from https://github.com/modrinth/theseus/blob/master/theseus/library/JavaInfo.class under MIT -#[tracing::instrument] -#[onelauncher_macros::memory] -pub async fn check_java_instance(path: &Path) -> Option { - let Ok(path) = io::canonicalize(path) else { - return None; - }; - let java = if path.file_name()?.to_str()? == JAVA_BIN { - path - } else { - path.join(JAVA_BIN) - }; - if !java.exists() { - return None; - } - - let bytes = include_bytes!("../../wrapper/java/JavaInfo.class"); - let tempdir: PathBuf = tempfile::tempdir().ok()?.into_path(); - if !tempdir.exists() { - return None; - } - - let file_path = tempdir.join("JavaInfo.class"); - tracing::info!( - "checking java with {}", - file_path.to_string_lossy().to_string() - ); - io::write(&file_path, bytes).await.ok()?; - - let output = Command::new(&java) - .arg("-cp") - .arg(file_path.parent().expect("failed to get java parent")) - .arg("JavaInfo") - .env_remove("_JAVA_OPTIONS") - .output() - .ok()?; - let stdout = String::from_utf8_lossy(&output.stdout); - tracing::debug!("{stdout}"); - - let mut java_version = None; - let mut java_arch = None; - - for line in stdout.lines() { - let mut parts = line.split('='); - let key = parts.next().unwrap_or_default(); - let value = parts.next().unwrap_or_default(); - - if key == "os.arch" { - java_arch = Some(value); - } else if key == "java.version" { - java_version = Some(value); - } - } - - if let Some(arch) = java_arch { - if let Some(version) = java_version { - let path = java.to_string_lossy().to_string(); - return Some(JavaVersion { - path, - version: version.to_string(), - arch: arch.to_string(), - }); - } - } - - None -} - -/// Get a formatted java version as `(major: u32, minor: u32)`. -/// -/// This means 1.8 becomes `(1, 8)`, and 21.0.3 becomes `(21, 0)`. -pub fn get_java_version(version: &str) -> Result<(u32, u32), JavaError> { - let mut split = version.split('.'); - let major_opt = split.next(); - let mut major; - let mut minor = if let Some(minor) = split.next() { - major = major_opt.unwrap_or("1").parse::()?; - minor.parse::()? - } else { - major = 1; - major_opt - .ok_or_else(|| JavaError::InvalidVersion(version.to_string()))? - .parse::()? - }; - - if major > 1 { - minor = major; - major = 1; - } - - Ok((major, minor)) -} - -/// An error wrapper for everything that can go wrong while managing Java installations. -#[derive(thiserror::Error, Debug)] -pub enum JavaError { - #[error("failed to fetch environment variable: {0}")] - EnvError(#[from] env::VarError), - - #[error("error running java command: {0}")] - IOError(#[from] std::io::Error), - - #[error("failed to parse integer: {0}")] - ParseError(#[from] std::num::ParseIntError), - - #[error("tokio error: {0}")] - JoinError(#[from] JoinError), - - #[error("couldn't get a valid java version from {0}")] - InvalidVersion(String), - - #[error("failed to get launcher state mutex!")] - MutexError, -} diff --git a/packages/core_old/src/utils/mod.rs b/packages/core_old/src/utils/mod.rs deleted file mode 100644 index 85093225..00000000 --- a/packages/core_old/src/utils/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! **`OneLauncher` Core Utilities** -//! Standard asynchronous utilities and wrappers for use within the launcher core. -//! -//! - [`http`]: Async extensions and wrappers around [`reqwest`] functions. -//! - [`java`]: Async utilities for managing and downloading Java versions. -//! - [`watcher`]: Async utilities for watching files with [`notify`]. - -pub mod http; -pub mod java; -pub mod pagination; -pub mod watcher; -pub mod crypto; - -#[cfg(feature = "tauri")] -pub mod window; diff --git a/packages/core_old/src/utils/pagination.rs b/packages/core_old/src/utils/pagination.rs deleted file mode 100644 index da740228..00000000 --- a/packages/core_old/src/utils/pagination.rs +++ /dev/null @@ -1,23 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[cfg_attr(feature = "specta", derive(specta::Type))] -#[derive(Default, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -#[allow(dead_code)] -pub struct Pagination { - pub index: u32, - pub page_size: u16, - pub result_count: u32, - pub total_count: u32, -} - -pub fn get_page_offset(page: Option, page_size: Option) -> (u32, u16) { - let page = page.unwrap_or(0); - let page_size = page_size.unwrap_or(10); - let offset = (page - 1) * page_size as u32; - (offset, page_size) -} - -pub fn get_page_count(total: u32, page_size: u16) -> u32 { - (total as f64 / page_size as f64).ceil() as u32 -} diff --git a/packages/core_old/src/utils/watcher.rs b/packages/core_old/src/utils/watcher.rs deleted file mode 100644 index 722973e1..00000000 --- a/packages/core_old/src/utils/watcher.rs +++ /dev/null @@ -1,86 +0,0 @@ -//! **Watcher Utilities** -//! -//! Async utilities for watching files with [`notify`]. - -use std::path::PathBuf; -use std::time::Duration; - -use crate::store::{Cluster, ClusterPath, Clusters}; -use futures::channel::mpsc::channel; -use futures::{SinkExt, StreamExt}; -use notify::RecommendedWatcher; -use notify_debouncer_mini::{new_debouncer, DebounceEventResult, Debouncer}; - -/// Creates and initializes an FS watcher for the `clusters` directory, returning -/// the watcher as a [`Debouncer`]. -pub fn initialize_watcher() -> crate::Result> { - let (mut sender, mut rscv) = channel(1); - let watcher = new_debouncer( - Duration::from_secs_f32(2.0), - move |result: DebounceEventResult| { - futures::executor::block_on(async { - sender - .send(result) - .await - .expect("failed to send debouse event"); - }); - }, - )?; - - tokio::task::spawn(async move { - let span = tracing::span!(tracing::Level::INFO, "initialize_watcher"); - tracing::info!(parent: &span, "initializing fs watcher..."); - while let Some(result) = rscv.next().await { - let _span = span.enter(); - - match result { - Ok(mut events) => { - let mut paths = Vec::new(); - events.sort_by(|a, b| a.path.cmp(&b.path)); - for a in &events { - let mut formatted = PathBuf::new(); - let mut components = a.path.components(); - let mut matched = false; - - for cmp in components.by_ref() { - formatted.push(cmp); - if matched { - break; - } - if cmp.as_os_str() == "clusters" { - matched = true; - } - } - - let sub = components.next().is_none(); - let cluster_path = ClusterPath::new(PathBuf::from( - formatted.file_name().unwrap_or_default(), - )); - - if a.path - .components() - .any(|c| c.as_os_str() == crate::constants::CRASH_PATH) - && a.path.extension().is_some_and(|e| e == "txt") - { - Cluster::handle_crash(cluster_path); - } else if !paths.contains(&formatted) { - if sub { - // TODO: Packages Figure this out - // let cluster_path = cluster_path.clone(); - // tokio::task::spawn(async move { - // Cluster::sync_packages(&cluster_path, true).await - // }); - paths.push(formatted); - } else { - Clusters::sync_cluster(cluster_path); - } - } - } - } - Err(err) => tracing::warn!("fs watching error: {err}"), - } - } - }); - - Ok(watcher) -} diff --git a/packages/core_old/src/utils/window.rs b/packages/core_old/src/utils/window.rs deleted file mode 100644 index 697effe8..00000000 --- a/packages/core_old/src/utils/window.rs +++ /dev/null @@ -1,19 +0,0 @@ -use tauri::WebviewWindow; - -pub fn set_window_styling( - win: &WebviewWindow, - custom: bool, -) -> Result<(), Box> { - win.set_decorations(!custom)?; - - #[cfg(target_os = "macos")] - { - if custom { - win.set_title_bar_style(tauri::TitleBarStyle::Overlay)?; - } else { - win.set_title_bar_style(tauri::TitleBarStyle::Visible)?; - } - } - - Ok(()) -} diff --git a/packages/core_old/wrapper/README.md b/packages/core_old/wrapper/README.md deleted file mode 100644 index dc228149..00000000 --- a/packages/core_old/wrapper/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Library Wrappers - -TODO: Don't pre-build binaries. That is a huge security issue. - -## `JavaCheck` - -A simple prebuilt Java class that prints the architecture and version of a JVM, [GitHub repository](https://github.com/PrismLauncher/PrismLauncher/tree/develop/libraries/javacheck) (Available under the Unlicense, sublicensed from ["Do what you want with it. It is so trivial that noone cares."](https://github.com/PrismLauncher/PrismLauncher/blob/develop/libraries/README.md#javacheck)) - -## `Gamemmode` - -A performance optimization daemon, [GitHub repository](https://github.com/FeralInteractive/gamemode). (Available under `BSD-3-Clause` on their GitHub repository) - -## `ForgeWrapper` - -An interface for the launching systems of modern Minecraft Forge, [GitHub repository](https://github.com/PrismLauncher/ForgeWrapper). (Available under `MIT` on their GitHub repository) diff --git a/packages/core_old/wrapper/forge/.gitkeep b/packages/core_old/wrapper/forge/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/core_old/wrapper/java/JavaInfo.java b/packages/core_old/wrapper/java/JavaInfo.java deleted file mode 100644 index 0d8571cf..00000000 --- a/packages/core_old/wrapper/java/JavaInfo.java +++ /dev/null @@ -1,19 +0,0 @@ -public final class JavaInfo { - private static final String[] CHECKED_PROPERTIES = new String[] {"os.arch", "java.version", "java.vendor"}; - - public static void main(String[] args) { - int returnCode = 0; - - for (String key : CHECKED_PROPERTIES) { - String property = System.getProperty(key); - - if (property != null) { - System.out.println(key + "=" + property); - } else { - returnCode = 1; - } - } - - System.exit(returnCode); - } -} diff --git a/packages/entity/src/domain/icon.rs b/packages/entity/src/domain/icon.rs index 89054a67..2f68713c 100644 --- a/packages/entity/src/domain/icon.rs +++ b/packages/entity/src/domain/icon.rs @@ -3,7 +3,8 @@ use std::{fmt::Display, ops::Deref, str::FromStr}; use sea_orm::{sea_query::Nullable, DeriveValueType}; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, DeriveValueType)] +// #[onelauncher_macro::specta] +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, DeriveValueType, specta::Type)] pub struct Icon(String); impl Nullable for Icon { @@ -73,7 +74,7 @@ pub enum IconType { impl Display for IconType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) + write!(f, "{self:?}") } } diff --git a/packages/entity/src/domain/resolution.rs b/packages/entity/src/domain/resolution.rs index a7ec1410..5ab9d27a 100644 --- a/packages/entity/src/domain/resolution.rs +++ b/packages/entity/src/domain/resolution.rs @@ -1,6 +1,7 @@ use sea_orm::{sea_query::{Nullable, ValueType}, TryGetableFromJson}; use serde::{Deserialize, Serialize}; +#[onelauncher_macro::specta] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct Resolution { pub width: u32, diff --git a/packages/entity/src/domain/settings.rs b/packages/entity/src/domain/settings.rs index 72e20684..156972a1 100644 --- a/packages/entity/src/domain/settings.rs +++ b/packages/entity/src/domain/settings.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; cfg_match! { target_os = "windows" => { + #[onelauncher_macro::specta] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct SettingsOsExtra { @@ -17,6 +18,7 @@ cfg_match! { } } target_os = "macos" => { + #[onelauncher_macro::specta] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct SettingsOsExtra { @@ -31,6 +33,7 @@ cfg_match! { } } not(any(target_os = "windows", target_os = "macos")) => { + #[onelauncher_macro::specta] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct SettingsOsExtra { pub enable_gamemode: Option, diff --git a/packages/entity/src/domain/utility.rs b/packages/entity/src/domain/utility.rs index 1c5dacb8..97cd515b 100644 --- a/packages/entity/src/domain/utility.rs +++ b/packages/entity/src/domain/utility.rs @@ -2,6 +2,7 @@ use std::ops::Deref; use serde::{de::DeserializeOwned, Deserialize, Serialize}; +#[onelauncher_macro::specta] #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] #[serde(transparent)] pub struct DbVec(pub Vec); diff --git a/packages/macro/Cargo.toml b/packages/macro/Cargo.toml index 6c54c347..e420b3de 100644 --- a/packages/macro/Cargo.toml +++ b/packages/macro/Cargo.toml @@ -22,3 +22,4 @@ specta = [] [dependencies] syn = { workspace = true } quote = { workspace = true } +proc-macro2 = { workspace = true } diff --git a/packages/macro/src/specta.rs b/packages/macro/src/specta.rs index de124454..1650fdd6 100644 --- a/packages/macro/src/specta.rs +++ b/packages/macro/src/specta.rs @@ -1,5 +1,5 @@ use quote::quote; -use syn::{meta::ParseNestedMeta, parse_macro_input, Item, Result}; +use syn::{meta::ParseNestedMeta, parse_macro_input, Item}; pub fn specta(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { let mut attrs = SpectaAttributes::default(); @@ -8,12 +8,12 @@ pub fn specta(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> p let input = parse_macro_input!(item as Item); - let specta_event_type = if attrs.with_event { + let specta_event_type = if attrs.event { quote! { #[cfg_attr(feature = "tauri", derive(tauri_specta::Event))] } } else { - quote! {} + proc_macro2::TokenStream::new() }; let specta_type = quote! { @@ -31,13 +31,13 @@ pub fn specta(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> p #[derive(Default)] struct SpectaAttributes { - with_event: bool, + event: bool, } impl SpectaAttributes { - fn parse(&mut self, meta: ParseNestedMeta) -> Result<()> { - if meta.path.is_ident("with_event") { - self.with_event = true; + fn parse(&mut self, meta: ParseNestedMeta) -> syn::Result<()> { + if meta.path.is_ident("event") { + self.event = true; } Ok(()) }