Inherit dependencies from workspace manifest, and optimize some out (#3655)

* chore: inherit dependencies from workspace, optimize some deps out

* Update bitflags from 2.9.0 to 2.9.1

* Fix temp directory leak in check_java_at_filepath

* Fix build

* Fix lint

* chore(app-lib): refactor overkill `futures` executor usage to Tokio MPSC

* chore: fix Clippy lint

* tweak: optimize out dependency on OpenSSL source build

Contrary to what I expected before, this was caused due to the Tauri
updater plugin using a different TLS stack than everything else.

* chore(labrinth): drop now unused dependency

* Update zip because 2.6.1 got yanked

* Downgrade weezl to 0.1.8

* Mention that p256 is also a blocker for rand 0.9

* chore: sidestep GitHub review requirements

* chore: sidestep GitHub review requirements (2)

* chore: sidestep GitHub review requirements (3)

---------

Co-authored-by: Josiah Glosson <soujournme@gmail.com>
This commit is contained in:
Alejandro González
2025-05-15 22:47:29 +02:00
committed by GitHub
parent 37cc81a36d
commit f19643095e
35 changed files with 876 additions and 1020 deletions

798
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +1,182 @@
[workspace] [workspace]
resolver = '2' resolver = "2"
members = [ members = [
'./packages/app-lib', "apps/app",
'./apps/app-playground', "apps/app-playground",
'./apps/app', "apps/daedalus_client",
'./apps/labrinth', "apps/labrinth",
'./apps/daedalus_client', "packages/app-lib",
'./packages/daedalus', "packages/ariadne",
'./packages/ariadne', "packages/daedalus",
] ]
# Optimize for speed and reduce size on release builds [workspace.dependencies]
[profile.release] actix-cors = "0.7.1"
panic = "abort" # Strip expensive panic clean-up logic actix-files = "0.6.6"
codegen-units = 1 # Compile crates one after another so the compiler can optimize better actix-http = "3.11.0"
lto = true # Enables link to optimizations actix-multipart = "0.7.2"
opt-level = "s" # Optimize for binary size actix-rt = "2.10.0"
strip = true # Remove debug symbols actix-web = "4.11.0"
actix-web-prom = "0.10.0"
[profile.dev.package.sqlx-macros] actix-ws = "0.3.0"
opt-level = 3 argon2 = { version = "0.5.3", features = ["std"] }
ariadne = { path = "packages/ariadne" }
async-compression = { version = "0.4.23", default-features = false }
async-recursion = "1.1.1"
async-stripe = { version = "0.41.0", default-features = false, features = [
"runtime-tokio-hyper-rustls",
] }
async-trait = "0.1.88"
async-tungstenite = { version = "0.29.1", default-features = false, features = [
"futures-03-sink",
] }
async-walkdir = "2.1.0"
async_zip = "0.0.17"
base64 = "0.22.1"
bitflags = "2.9.0"
bytes = "1.10.1"
censor = "0.3.0"
chrono = "0.4.41"
clap = "4.5.38"
clickhouse = "0.13.2"
color-thief = "0.2.2"
console-subscriber = "0.4.1"
daedalus = { path = "packages/daedalus" }
dashmap = "6.1.0"
deadpool-redis = "0.20.0"
dirs = "6.0.0"
discord-rich-presence = "0.2.5"
dotenv-build = "0.1.1"
dotenvy = "0.15.7"
dunce = "1.0.5"
either = "1.15.0"
enumset = "1.1.6"
flate2 = "1.1.1"
fs4 = { version = "0.13.1", default-features = false }
futures = { version = "0.3.31", default-features = false }
futures-util = "0.3.31"
hex = "0.4.3"
hickory-resolver = "0.25.2"
hmac = "0.12.1"
hyper-tls = "0.6.0"
hyper-util = "0.1.11"
iana-time-zone = "0.1.63"
image = { version = "0.25.6", default-features = false, features = ["rayon"] }
indexmap = "2.9.0"
indicatif = "0.17.11"
itertools = "0.14.0"
jemalloc_pprof = "0.7.0"
json-patch = { version = "4.0.0", default-features = false }
lettre = { version = "0.11.16", default-features = false, features = [
"builder",
"hostname",
"pool",
"ring",
"rustls",
"rustls-native-certs",
"smtp-transport",
] }
maxminddb = "0.26.0"
meilisearch-sdk = { version = "0.28.0", default-features = false }
murmur2 = "0.1.0"
native-dialog = "0.9.0"
notify = { version = "8.0.0", default-features = false }
notify-debouncer-mini = { version = "0.6.0", default-features = false }
p256 = "0.13.2"
paste = "1.0.15"
prometheus = "0.14.0"
quartz_nbt = "0.2.9"
quick-xml = "0.37.5"
rand = "=0.8.5" # Locked on 0.8 until argon2 and p256 update to 0.9
rand_chacha = "=0.3.1" # Locked on 0.3 until we can update rand to 0.9
redis = "=0.29.5" # Locked on 0.29 until deadpool-redis updates to 0.30
regex = "1.11.1"
reqwest = { version = "0.12.15", default-features = false }
rust-s3 = { version = "0.35.1", default-features = false, features = [
"fail-on-err",
"tags",
"tokio-rustls-tls",
] }
rust_decimal = { version = "1.37.1", features = [
"serde-with-float",
"serde-with-str",
] }
rust_iso3166 = "0.1.14"
rusty-money = "0.4.1"
sentry = { version = "0.38.1", default-features = false, features = [
"backtrace",
"contexts",
"debug-images",
"panic",
"reqwest",
"rustls",
] }
sentry-actix = "0.38.1"
serde = "1.0.219"
serde-xml-rs = "0.8.0" # Also an XML (de)serializer, consider dropping yaserde in favor of this
serde_bytes = "0.11.17"
serde_cbor = "0.11.2"
serde_ini = "0.2.0"
serde_json = "1.0.140"
serde_with = "3.12.0"
sha1 = "0.10.6"
sha1_smol = { version = "1.0.1", features = ["std"] }
sha2 = "0.10.9"
spdx = "0.10.8"
sqlx = { version = "0.8.5", default-features = false }
sysinfo = { version = "0.35.1", default-features = false }
tar = "0.4.44"
tauri = "2.5.1"
tauri-build = "2.2.0"
tauri-plugin-deep-link = "2.2.1"
tauri-plugin-dialog = "2.2.1"
tauri-plugin-opener = "2.2.6"
tauri-plugin-os = "2.2.1"
tauri-plugin-single-instance = "2.2.3"
tauri-plugin-updater = { version = "2.7.1", default-features = false, features = [
"rustls-tls",
] }
tauri-plugin-window-state = "2.2.2"
tempfile = "3.20.0"
theseus = { path = "packages/app-lib" }
thiserror = "2.0.12"
tikv-jemalloc-ctl = "0.6.0"
tikv-jemallocator = "0.6.0"
tokio = "1.45.0"
tokio-stream = "0.1.17"
tokio-util = "0.7.15"
totp-rs = "5.7.0"
tracing = "0.1.41"
tracing-actix-web = "0.7.18"
tracing-error = "0.2.1"
tracing-subscriber = "0.3.19"
url = "2.5.4"
urlencoding = "2.1.3"
uuid = "1.16.0"
validator = "0.20.0"
webp = { version = "0.3.0", default-features = false }
whoami = "1.6.0"
winreg = "0.55.0"
woothee = "0.13.0"
yaserde = "0.12.0"
zip = { version = "3.0.0", default-features = false, features = [
"bzip2",
"deflate",
"deflate64",
"zstd",
] }
zxcvbn = "3.1.0"
[patch.crates-io] [patch.crates-io]
wry = { git = "https://github.com/modrinth/wry", rev = "cafdaa9" } wry = { git = "https://github.com/modrinth/wry", rev = "cafdaa9" }
# Optimize for speed and reduce size on release builds
[profile.release]
opt-level = "s" # Optimize for binary size
strip = true # Remove debug symbols
lto = true # Enables link to optimizations
panic = "abort" # Strip expensive panic clean-up logic
codegen-units = 1 # Compile crates one after another so the compiler can optimize better
[profile.dev.package.sqlx-macros]
opt-level = 3

View File

@@ -6,9 +6,6 @@ edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
theseus = { path = "../../packages/app-lib", features = ["cli"] } theseus = { workspace = true, features = ["cli"] }
tokio = { version = "1", features = ["full"] } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
webbrowser = "1.0.4" enumset.workspace = true
enumset = "1.1"
tracing = "0.1.37"

View File

@@ -15,8 +15,7 @@ pub async fn authenticate_run() -> theseus::Result<Credentials> {
println!("A browser window will now open, follow the login flow there."); println!("A browser window will now open, follow the login flow there.");
let login = minecraft_auth::begin_login().await?; let login = minecraft_auth::begin_login().await?;
println!("URL {}", login.redirect_uri.as_str()); println!("Open URL {} in a browser", login.redirect_uri.as_str());
webbrowser::open(login.redirect_uri.as_str())?;
println!("Please enter URL code: "); println!("Please enter URL code: ");
let mut input = String::new(); let mut input = String::new();

View File

@@ -8,47 +8,45 @@ edition = "2024"
build = "build.rs" build = "build.rs"
[build-dependencies] [build-dependencies]
tauri-build = { version = "2.2.0", features = ["codegen"] } tauri-build = { workspace = true, features = ["codegen"] }
[dependencies] [dependencies]
theseus = { path = "../../packages/app-lib", features = ["tauri"] } theseus = { workspace = true, features = ["tauri"] }
serde_json = "1.0" serde_json.workspace = true
serde = { version = "1.0", features = ["derive"] } serde = { workspace = true, features = ["derive"] }
serde_with = "3.0.0" serde_with.workspace = true
tauri = { version = "2.5.1", features = ["devtools", "macos-private-api", "protocol-asset", "unstable"] } tauri = { workspace = true, features = ["devtools", "macos-private-api", "protocol-asset", "unstable"] }
tauri-plugin-window-state = "2.2.0" tauri-plugin-window-state.workspace = true
tauri-plugin-deep-link = "2.2.0" tauri-plugin-deep-link.workspace = true
tauri-plugin-os = "2.2.0" tauri-plugin-os.workspace = true
tauri-plugin-opener = "2.2.6" tauri-plugin-opener.workspace = true
tauri-plugin-dialog = "2.2.0" tauri-plugin-dialog.workspace = true
tauri-plugin-updater = { version = "2.3.0" } tauri-plugin-updater.workspace = true
tauri-plugin-single-instance = { version = "2.2.0" } tauri-plugin-single-instance.workspace = true
tokio = { version = "1", features = ["full"] } tokio = { workspace = true, features = ["time"] }
thiserror = "2.0.12" thiserror.workspace = true
daedalus = { path = "../../packages/daedalus" } daedalus.workspace = true
chrono = "0.4.26" chrono.workspace = true
either = "1.15" either.workspace = true
url = "2.2" url.workspace = true
urlencoding = "2.1" urlencoding.workspace = true
uuid = { version = "1.1", features = ["serde", "v4"] } uuid = { workspace = true, features = ["serde", "v4"] }
tracing = "0.1.37" tracing.workspace = true
tracing-error = "0.2.0" tracing-error.workspace = true
dashmap = "6.1.0" dashmap.workspace = true
paste = "1.0.15" paste.workspace = true
enumset = { version = "1.1", features = ["serde"] } enumset = { workspace = true, features = ["serde"] }
opener = { version = "0.7.2", features = ["reveal", "dbus-vendored"] } native-dialog.workspace = true
native-dialog = "0.9.0"
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
tauri-plugin-updater = { version = "2.3.0", optional = true, features = ["native-tls-vendored", "zip"], default-features = false } tauri-plugin-updater = { workspace = true, optional = true }
[features] [features]
# by default Tauri runs in production mode # by default Tauri runs in production mode

View File

@@ -1,4 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tauri::Runtime;
use tauri_plugin_opener::OpenerExt;
use theseus::{ use theseus::{
handler, handler,
prelude::{CommandPayload, DirectoryInfo}, prelude::{CommandPayload, DirectoryInfo},
@@ -74,29 +76,29 @@ pub async fn should_disable_mouseover() -> bool {
} }
#[tauri::command] #[tauri::command]
pub fn highlight_in_folder(path: PathBuf) { pub fn highlight_in_folder<R: Runtime>(
let res = opener::reveal(path); app: tauri::AppHandle<R>,
path: PathBuf,
if let Err(e) = res { ) {
if let Err(e) = app.opener().reveal_item_in_dir(path) {
tracing::error!("Failed to highlight file in folder: {}", e); tracing::error!("Failed to highlight file in folder: {}", e);
} }
} }
#[tauri::command] #[tauri::command]
pub fn open_path(path: PathBuf) { pub fn open_path<R: Runtime>(app: tauri::AppHandle<R>, path: PathBuf) {
let res = opener::open(path); if let Err(e) = app.opener().open_path(path.to_string_lossy(), None::<&str>)
{
if let Err(e) = res {
tracing::error!("Failed to open path: {}", e); tracing::error!("Failed to open path: {}", e);
} }
} }
#[tauri::command] #[tauri::command]
pub fn show_launcher_logs_folder() { pub fn show_launcher_logs_folder<R: Runtime>(app: tauri::AppHandle<R>) {
let path = DirectoryInfo::launcher_logs_dir().unwrap_or_default(); let path = DirectoryInfo::launcher_logs_dir().unwrap_or_default();
// failure to get folder just opens filesystem // failure to get folder just opens filesystem
// (ie: if in debug mode only and launcher_logs never created) // (ie: if in debug mode only and launcher_logs never created)
open_path(path); open_path(app, path);
} }
// Get opening command // Get opening command

View File

@@ -7,25 +7,24 @@ edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
daedalus = { path = "../../packages/daedalus" } daedalus.workspace = true
tokio = { version = "1", features = ["full"] } tokio = { workspace = true, features = ["sync", "macros", "rt-multi-thread"] }
futures = "0.3.25" futures.workspace = true
dotenvy = "0.15.6" dotenvy.workspace = true
serde = { version = "1.0", features = ["derive"] } serde = { workspace = true, features = ["derive"] }
serde_json = "1.0" serde_json.workspace = true
serde-xml-rs = "0.6.0" serde-xml-rs.workspace = true
lazy_static = "1.4.0" thiserror.workspace = true
thiserror = "2.0" reqwest = { workspace = true, features = ["stream", "json", "rustls-tls-native-roots"] }
reqwest = { version = "0.12.15", default-features = false, features = ["stream", "json", "rustls-tls-native-roots"] } async_zip = { workspace = true, features = ["chrono", "tokio-fs", "deflate", "bzip2", "zstd", "deflate64"] }
async_zip = { version = "0.0.17", features = ["full"] } chrono = { workspace = true, features = ["serde"] }
chrono = { version = "0.4", features = ["serde"] } bytes.workspace = true
bytes = "1.6.0" rust-s3.workspace = true
rust-s3 = { version = "0.35.1", default-features = false, features = ["fail-on-err", "tags", "tokio-rustls-tls"] } dashmap.workspace = true
dashmap = "6.1.0" sha1_smol.workspace = true
sha1_smol = { version = "1.0.0", features = ["std"] } indexmap = { workspace = true, features = ["serde"] }
indexmap = { version = "2.2.6", features = ["serde"] } itertools.workspace = true
itertools = "0.14.0" tracing-error.workspace = true
tracing-error = "0.2.0"
tracing = "0.1" tracing.workspace = true
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { workspace = true, features = ["env-filter"] }

View File

@@ -3,59 +3,57 @@ use bytes::Bytes;
use s3::creds::Credentials; use s3::creds::Credentials;
use s3::{Bucket, Region}; use s3::{Bucket, Region};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use std::sync::Arc; use std::sync::{Arc, LazyLock};
use tokio::sync::Semaphore; use tokio::sync::Semaphore;
lazy_static::lazy_static! { static BUCKET: LazyLock<Bucket> = LazyLock::new(|| {
static ref BUCKET: Bucket = { let region = dotenvy::var("S3_REGION").unwrap();
let region = dotenvy::var("S3_REGION").unwrap(); let b = Bucket::new(
let b = Bucket::new( &dotenvy::var("S3_BUCKET_NAME").unwrap(),
&dotenvy::var("S3_BUCKET_NAME").unwrap(), if &*region == "r2" {
if &*region == "r2" { Region::R2 {
Region::R2 { account_id: dotenvy::var("S3_URL").unwrap(),
account_id: dotenvy::var("S3_URL").unwrap(), }
}
} else {
Region::Custom {
region: region.clone(),
endpoint: dotenvy::var("S3_URL").unwrap(),
}
},
Credentials::new(
Some(&*dotenvy::var("S3_ACCESS_TOKEN").unwrap()),
Some(&*dotenvy::var("S3_SECRET").unwrap()),
None,
None,
None,
).unwrap(),
).unwrap();
if region == "path-style" {
*b.with_path_style()
} else { } else {
*b Region::Custom {
} region: region.clone(),
}; endpoint: dotenvy::var("S3_URL").unwrap(),
} }
},
Credentials::new(
Some(&*dotenvy::var("S3_ACCESS_TOKEN").unwrap()),
Some(&*dotenvy::var("S3_SECRET").unwrap()),
None,
None,
None,
)
.unwrap(),
)
.unwrap();
lazy_static::lazy_static! { if region == "path-style" {
pub static ref REQWEST_CLIENT: reqwest::Client = { *b.with_path_style()
let mut headers = reqwest::header::HeaderMap::new(); } else {
if let Ok(header) = reqwest::header::HeaderValue::from_str(&format!( *b
"modrinth/daedalus/{} (support@modrinth.com)", }
env!("CARGO_PKG_VERSION") });
)) {
headers.insert(reqwest::header::USER_AGENT, header);
}
reqwest::Client::builder() pub static REQWEST_CLIENT: LazyLock<reqwest::Client> = LazyLock::new(|| {
.tcp_keepalive(Some(std::time::Duration::from_secs(10))) let mut headers = reqwest::header::HeaderMap::new();
.timeout(std::time::Duration::from_secs(15)) if let Ok(header) = reqwest::header::HeaderValue::from_str(&format!(
.default_headers(headers) "modrinth/daedalus/{} (support@modrinth.com)",
.build() env!("CARGO_PKG_VERSION")
.unwrap() )) {
}; headers.insert(reqwest::header::USER_AGENT, header);
} }
reqwest::Client::builder()
.tcp_keepalive(Some(std::time::Duration::from_secs(10)))
.timeout(std::time::Duration::from_secs(15))
.default_headers(headers)
.build()
.unwrap()
});
#[tracing::instrument(skip(bytes, semaphore))] #[tracing::instrument(skip(bytes, semaphore))]
pub async fn upload_file_to_bucket( pub async fn upload_file_to_bucket(

View File

@@ -11,74 +11,72 @@ name = "labrinth"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
actix-web = "4.10.2" actix-web.workspace = true
actix-rt = "2.9.0" actix-rt.workspace = true
actix-multipart = "0.7.2" actix-multipart.workspace = true
actix-cors = "0.7.1" actix-cors.workspace = true
actix-ws = "0.3.0" actix-ws.workspace = true
actix-files = "0.6.5" actix-files.workspace = true
prometheus = "0.13.4" # Locked on 0.13 until actix updates to 0.14 prometheus.workspace = true
actix-web-prom = { version = "0.9.0", features = ["process"] } actix-web-prom = { workspace = true, features = ["process"] }
tracing = "0.1.41" tracing.workspace = true
tracing-actix-web = "0.7.18" tracing-actix-web.workspace = true
console-subscriber = "0.4.1" console-subscriber.workspace = true
tokio = { version = "1.35.1", features = ["sync", "rt-multi-thread"] } tokio = { workspace = true, features = ["sync", "rt-multi-thread"] }
tokio-stream = "0.1.14" tokio-stream.workspace = true
futures = "0.3.30" futures.workspace = true
futures-util = "0.3.30" futures-util.workspace = true
async-trait = "0.1.70" async-trait.workspace = true
dashmap = "6.1.0" dashmap.workspace = true
lazy_static = "1.4.0"
meilisearch-sdk = "0.28.0" meilisearch-sdk = { workspace = true, features = ["reqwest"] }
rust-s3 = { version = "0.35.1", default-features = false, features = ["fail-on-err", "tags", "tokio-rustls-tls"] } rust-s3.workspace = true
reqwest = { version = "0.12.15", features = ["json", "multipart"] } reqwest = { workspace = true, features = ["http2", "rustls-tls-webpki-roots", "json", "multipart"] }
hyper = { version = "1.6", features = ["full"] } hyper-tls.workspace = true
hyper-tls = "0.6.0" hyper-util.workspace = true
hyper-util = "0.1.11"
serde = { version = "1.0", features = ["derive"] } serde = { workspace = true, features = ["derive"] }
serde_json = "1.0" serde_json.workspace = true
serde_with = "3.0.0" serde_with.workspace = true
chrono = { version = "0.4.26", features = ["serde"] } chrono = { workspace = true, features = ["serde"] }
yaserde = "0.12.0" yaserde = { workspace = true, features = ["derive"] }
yaserde_derive = "0.12.0"
rand = "0.8.5" # Locked on 0.8 until argon2 updates to 0.9 rand.workspace = true
rand_chacha = "0.3.1" # Locked on 0.3 until we can update rand to 0.9 rand_chacha.workspace = true
bytes = "1.4.0" bytes.workspace = true
base64 = "0.22.1" base64.workspace = true
sha1 = { version = "0.10.6", features = ["std"] } sha1.workspace = true
sha2 = "0.10.9" sha2.workspace = true
hmac = "0.12.1" hmac.workspace = true
argon2 = { version = "0.5.0", features = ["std"] } argon2.workspace = true
murmur2 = "0.1.0" murmur2.workspace = true
bitflags = "2.4.0" bitflags.workspace = true
hex = "0.4.3" hex.workspace = true
zxcvbn = "3.1.0" zxcvbn.workspace = true
totp-rs = { version = "5.0.2", features = ["gen_secret"] } totp-rs = { workspace = true, features = ["gen_secret"] }
url = "2.4.0" url.workspace = true
urlencoding = "2.1.2" urlencoding.workspace = true
zip = "2.6.1" zip.workspace = true
itertools = "0.14.0" itertools.workspace = true
validator = { version = "0.20.0", features = ["derive"] } validator = { workspace = true, features = ["derive"] }
regex = "1.10.2" regex.workspace = true
censor = "0.3.0" censor.workspace = true
spdx = { version = "0.10.3", features = ["text"] } spdx = { workspace = true, features = ["text"] }
dotenvy = "0.15.7" dotenvy.workspace = true
thiserror = "2.0.12" thiserror.workspace = true
either = "1.13" either.workspace = true
sqlx = { version = "0.8.2", features = [ sqlx = { workspace = true, features = [
"runtime-tokio-rustls", "runtime-tokio",
"tls-rustls-ring",
"postgres", "postgres",
"chrono", "chrono",
"macros", "macros",
@@ -86,51 +84,49 @@ sqlx = { version = "0.8.2", features = [
"rust_decimal", "rust_decimal",
"json", "json",
] } ] }
rust_decimal = { version = "1.33.1", features = [ rust_decimal = { workspace = true, features = [
"serde-with-float", "serde-with-float",
"serde-with-str", "serde-with-str",
] } ] }
redis = { version = "0.29.5", features = ["tokio-comp", "ahash", "r2d2"] } # Locked on 0.29 until deadpool-redis updates to 0.30 redis = { workspace = true, features = ["tokio-comp", "ahash", "r2d2"] } # Locked on 0.29 until deadpool-redis updates to 0.30
deadpool-redis = "0.20.0" deadpool-redis.workspace = true
clickhouse = { version = "0.13.2", features = ["uuid", "time"] } clickhouse = { workspace = true, features = ["uuid", "time"] }
uuid = { version = "1.2.2", features = ["v4", "fast-rng", "serde"] } uuid = { workspace = true, features = ["v4", "fast-rng", "serde"] }
maxminddb = "0.26.0" maxminddb.workspace = true
flate2 = "1.0.25" flate2.workspace = true
tar = "0.4.38" tar.workspace = true
sentry = { version = "0.37.0", default-features = false, features = ["backtrace", "contexts", "debug-images", "panic", "rustls", "reqwest"] } sentry.workspace = true
sentry-actix = "0.37.0" sentry-actix.workspace = true
image = "0.25.6" image = { workspace = true, features = ["avif", "bmp", "dds", "exr", "ff", "gif", "hdr", "ico", "jpeg", "png", "pnm", "qoi", "tga", "tiff", "webp"] }
color-thief = "0.2.2" color-thief.workspace = true
webp = "0.3.0" webp.workspace = true
woothee = "0.13.0" woothee.workspace = true
lettre = "0.11.3" lettre.workspace = true
derive-new = "0.7.0" rust_iso3166.workspace = true
rust_iso3166 = "0.1.11"
async-stripe = { version = "0.41.0", features = ["runtime-tokio-hyper-rustls"] } async-stripe = { workspace = true, features = ["billing", "checkout", "connect", "webhook-events"] }
rusty-money = "0.4.1" rusty-money.workspace = true
json-patch = "4.0.0" json-patch.workspace = true
ariadne = { path = "../../packages/ariadne" } ariadne.workspace = true
clap = { version = "4.5", features = ["derive"] } clap = { workspace = true, features = ["derive"] }
iana-time-zone = "0.1.61"
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
tikv-jemallocator = { version = "0.6.0", features = ["profiling", "unprefixed_malloc_on_supported_platforms"] } tikv-jemallocator = { workspace = true, features = ["profiling", "unprefixed_malloc_on_supported_platforms"] }
tikv-jemalloc-ctl = { version = "0.6.0", features = ["stats"] } tikv-jemalloc-ctl = { workspace = true, features = ["stats"] }
jemalloc_pprof = { version = "0.7.0", features = ["flamegraph"] } jemalloc_pprof = { workspace = true, features = ["flamegraph"] }
[dev-dependencies] [dev-dependencies]
actix-http = "3.4.0" actix-http.workspace = true
[build-dependencies] [build-dependencies]
dotenv-build = "0.1.1" dotenv-build.workspace = true
chrono = "0.4.38" chrono.workspace = true
iana-time-zone = "0.1.60" iana-time-zone.workspace = true

View File

@@ -111,10 +111,10 @@ pub async fn init_oauth(
.map_err(|e| { .map_err(|e| {
OAuthError::redirect(e, &oauth_info.state, &redirect_uri) OAuthError::redirect(e, &oauth_info.state, &redirect_uri)
})?; })?;
let redirect_uris = OAuthRedirectUris::new( let redirect_uris = OAuthRedirectUris {
oauth_info.redirect_uri.clone(), original: oauth_info.redirect_uri.clone(),
redirect_uri.clone(), validated: redirect_uri.clone(),
); };
match existing_authorization { match existing_authorization {
Some(existing_authorization) Some(existing_authorization)
if existing_authorization.scopes.contains(requested_scopes) => if existing_authorization.scopes.contains(requested_scopes) =>

View File

@@ -3,7 +3,7 @@ use crate::auth::oauth::OAuthErrorType;
use crate::database::models::OAuthClientId; use crate::database::models::OAuthClientId;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(derive_new::new, Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct OAuthRedirectUris { pub struct OAuthRedirectUris {
pub original: Option<String>, pub original: Option<String>,
pub validated: ValidatedRedirectUri, pub validated: ValidatedRedirectUri,

View File

@@ -132,6 +132,8 @@ pub async fn payouts(
} }
mod version_updater { mod version_updater {
use std::sync::LazyLock;
use crate::database::models::legacy_loader_fields::MinecraftGameVersion; use crate::database::models::legacy_loader_fields::MinecraftGameVersion;
use crate::database::redis::RedisPool; use crate::database::redis::RedisPool;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
@@ -197,36 +199,45 @@ mod version_updater {
("3D Shareware v1.34", "3D-Shareware-v1.34"), ("3D Shareware v1.34", "3D-Shareware-v1.34"),
]; ];
lazy_static::lazy_static! { /// Mojank for some reason has versions released at the same DateTime. This hardcodes them to fix this,
/// Mojank for some reason has versions released at the same DateTime. This hardcodes them to fix this, /// as most of our ordering logic is with DateTime
/// as most of our ordering logic is with DateTime static HALL_OF_SHAME_2: LazyLock<[(&'static str, DateTime<Utc>); 4]> =
static ref HALL_OF_SHAME_2: [(&'static str, DateTime<Utc>); 4] = [ LazyLock::new(|| {
( [
"1.4.5", (
chrono::DateTime::parse_from_rfc3339("2012-12-19T22:00:00+00:00") "1.4.5",
chrono::DateTime::parse_from_rfc3339(
"2012-12-19T22:00:00+00:00",
)
.unwrap() .unwrap()
.into(), .into(),
), ),
( (
"1.4.6", "1.4.6",
chrono::DateTime::parse_from_rfc3339("2012-12-19T22:00:01+00:00") chrono::DateTime::parse_from_rfc3339(
"2012-12-19T22:00:01+00:00",
)
.unwrap() .unwrap()
.into(), .into(),
), ),
( (
"1.6.3", "1.6.3",
chrono::DateTime::parse_from_rfc3339("2013-09-13T10:54:41+00:00") chrono::DateTime::parse_from_rfc3339(
"2013-09-13T10:54:41+00:00",
)
.unwrap() .unwrap()
.into(), .into(),
), ),
( (
"13w37b", "13w37b",
chrono::DateTime::parse_from_rfc3339("2013-09-13T10:54:42+00:00") chrono::DateTime::parse_from_rfc3339(
"2013-09-13T10:54:42+00:00",
)
.unwrap() .unwrap()
.into(), .into(),
), ),
]; ]
} });
for version in input.versions.into_iter() { for version in input.versions.into_iter() {
let mut name = version.id; let mut name = version.id;

View File

@@ -117,11 +117,10 @@ impl GalleryItem {
} }
} }
#[derive(derive_new::new)]
pub struct ModCategory { pub struct ModCategory {
project_id: ProjectId, pub project_id: ProjectId,
category_id: CategoryId, pub category_id: CategoryId,
is_additional: bool, pub is_additional: bool,
} }
impl ModCategory { impl ModCategory {
@@ -245,12 +244,18 @@ impl ProjectBuilder {
let project_id = self.project_id; let project_id = self.project_id;
let mod_categories = categories let mod_categories = categories
.into_iter() .into_iter()
.map(|c| ModCategory::new(project_id, c, false)) .map(|category_id| ModCategory {
.chain( project_id,
additional_categories category_id,
.into_iter() is_additional: false,
.map(|c| ModCategory::new(project_id, c, true)), })
) .chain(additional_categories.into_iter().map(|category_id| {
ModCategory {
project_id,
category_id,
is_additional: true,
}
}))
.collect_vec(); .collect_vec();
ModCategory::insert_many(mod_categories, &mut *transaction).await?; ModCategory::insert_many(mod_categories, &mut *transaction).await?;

View File

@@ -229,7 +229,10 @@ impl VersionBuilder {
let loader_versions = loaders let loader_versions = loaders
.iter() .iter()
.map(|l| LoaderVersion::new(*l, version_id)) .map(|&loader_id| LoaderVersion {
loader_id,
version_id,
})
.collect_vec(); .collect_vec();
LoaderVersion::insert_many(loader_versions, transaction).await?; LoaderVersion::insert_many(loader_versions, transaction).await?;
@@ -239,7 +242,7 @@ impl VersionBuilder {
} }
} }
#[derive(derive_new::new, Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct LoaderVersion { pub struct LoaderVersion {
pub loader_id: LoaderId, pub loader_id: LoaderId,
pub version_id: VersionId, pub version_id: VersionId,

View File

@@ -152,7 +152,7 @@ async fn main() -> std::io::Result<()> {
.expect("Failed to register redis metrics"); .expect("Failed to register redis metrics");
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
labrinth::routes::debug::jemalloc_mmeory_stats(&prometheus.registry) labrinth::routes::debug::jemalloc_memory_stats(&prometheus.registry)
.expect("Failed to register jemalloc metrics"); .expect("Failed to register jemalloc metrics");
let labrinth_config = labrinth::app_setup( let labrinth_config = labrinth::app_setup(

View File

@@ -50,7 +50,7 @@ fn require_profiling_activated(
} }
} }
pub fn jemalloc_mmeory_stats( pub fn jemalloc_memory_stats(
registry: &Registry, registry: &Registry,
) -> Result<(), prometheus::Error> { ) -> Result<(), prometheus::Error> {
let allocated_mem = IntGauge::new( let allocated_mem = IntGauge::new(

View File

@@ -13,7 +13,7 @@ use crate::util::captcha::check_hcaptcha;
use crate::util::env::parse_strings_from_var; use crate::util::env::parse_strings_from_var;
use crate::util::ext::get_image_ext; use crate::util::ext::get_image_ext;
use crate::util::img::upload_image_optimized; use crate::util::img::upload_image_optimized;
use crate::util::validate::{RE_URL_SAFE, validation_errors_to_string}; use crate::util::validate::validation_errors_to_string;
use actix_web::web::{Data, Query, ServiceConfig, scope}; use actix_web::web::{Data, Query, ServiceConfig, scope};
use actix_web::{HttpRequest, HttpResponse, delete, get, patch, post, web}; use actix_web::{HttpRequest, HttpResponse, delete, get, patch, post, web};
use argon2::password_hash::SaltString; use argon2::password_hash::SaltString;
@@ -1318,7 +1318,7 @@ pub async fn sign_up_sendy(email: &str) -> Result<(), AuthenticationError> {
#[derive(Deserialize, Validate)] #[derive(Deserialize, Validate)]
pub struct NewAccount { pub struct NewAccount {
#[validate(length(min = 1, max = 39), regex(path = *RE_URL_SAFE))] #[validate(length(min = 1, max = 39), regex(path = *crate::util::validate::RE_URL_SAFE))]
pub username: String, pub username: String,
#[validate(length(min = 8, max = 256))] #[validate(length(min = 8, max = 256))]
pub password: String, pub password: String,

View File

@@ -12,7 +12,7 @@ use crate::{auth::get_user_from_headers, database};
use actix_web::{HttpRequest, HttpResponse, get, route, web}; use actix_web::{HttpRequest, HttpResponse, get, route, web};
use sqlx::PgPool; use sqlx::PgPool;
use std::collections::HashSet; use std::collections::HashSet;
use yaserde_derive::YaSerialize; use yaserde::YaSerialize;
pub fn config(cfg: &mut web::ServiceConfig) { pub fn config(cfg: &mut web::ServiceConfig) {
cfg.service(maven_metadata); cfg.service(maven_metadata);

View File

@@ -9,8 +9,6 @@ use crate::models::v2::user::LegacyUser;
use crate::queue::session::AuthQueue; use crate::queue::session::AuthQueue;
use crate::routes::{ApiError, v2_reroute, v3}; use crate::routes::{ApiError, v2_reroute, v3};
use actix_web::{HttpRequest, HttpResponse, delete, get, patch, web}; use actix_web::{HttpRequest, HttpResponse, delete, get, patch, web};
use lazy_static::lazy_static;
use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::PgPool; use sqlx::PgPool;
use std::sync::Arc; use std::sync::Arc;
@@ -135,20 +133,16 @@ pub async fn projects_list(
} }
} }
lazy_static! {
static ref RE_URL_SAFE: Regex = Regex::new(r"^[a-zA-Z0-9_-]*$").unwrap();
}
#[derive(Serialize, Deserialize, Validate)] #[derive(Serialize, Deserialize, Validate)]
pub struct EditUser { pub struct EditUser {
#[validate(length(min = 1, max = 39), regex(path = *RE_URL_SAFE))] #[validate(length(min = 1, max = 39), regex(path = *crate::util::validate::RE_URL_SAFE))]
pub username: Option<String>, pub username: Option<String>,
#[serde( #[serde(
default, default,
skip_serializing_if = "Option::is_none", skip_serializing_if = "Option::is_none",
with = "::serde_with::rust::double_option" with = "::serde_with::rust::double_option"
)] )]
#[validate(length(min = 1, max = 64), regex(path = *RE_URL_SAFE))] #[validate(length(min = 1, max = 64), regex(path = *crate::util::validate::RE_URL_SAFE))]
pub name: Option<Option<String>>, pub name: Option<Option<String>>,
#[serde( #[serde(
default, default,

View File

@@ -906,11 +906,11 @@ pub async fn edit_project_categories(
categories: &Vec<String>, categories: &Vec<String>,
perms: &ProjectPermissions, perms: &ProjectPermissions,
project_id: db_ids::ProjectId, project_id: db_ids::ProjectId,
additional: bool, is_additional: bool,
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<(), ApiError> { ) -> Result<(), ApiError> {
if !perms.contains(ProjectPermissions::EDIT_DETAILS) { if !perms.contains(ProjectPermissions::EDIT_DETAILS) {
let additional_str = if additional { "additional " } else { "" }; let additional_str = if is_additional { "additional " } else { "" };
return Err(ApiError::CustomAuthentication(format!( return Err(ApiError::CustomAuthentication(format!(
"You do not have the permissions to edit the {additional_str}categories of this project!" "You do not have the permissions to edit the {additional_str}categories of this project!"
))); )));
@@ -928,7 +928,11 @@ pub async fn edit_project_categories(
let mcategories = category_ids let mcategories = category_ids
.values() .values()
.map(|x| ModCategory::new(project_id, *x, additional)) .map(|&category_id| ModCategory {
project_id,
category_id,
is_additional,
})
.collect::<Vec<_>>(); .collect::<Vec<_>>();
mod_categories.extend(mcategories); mod_categories.extend(mcategories);
} }
@@ -1081,7 +1085,6 @@ pub async fn dependency_list(
} }
} }
#[derive(derive_new::new)]
pub struct CategoryChanges<'a> { pub struct CategoryChanges<'a> {
pub categories: &'a Option<Vec<String>>, pub categories: &'a Option<Vec<String>>,
pub add_categories: &'a Option<Vec<String>>, pub add_categories: &'a Option<Vec<String>>,
@@ -1241,11 +1244,11 @@ pub async fn projects_edit(
&categories, &categories,
&project.categories, &project.categories,
project.inner.id as db_ids::ProjectId, project.inner.id as db_ids::ProjectId,
CategoryChanges::new( CategoryChanges {
&bulk_edit_project.categories, categories: &bulk_edit_project.categories,
&bulk_edit_project.add_categories, add_categories: &bulk_edit_project.add_categories,
&bulk_edit_project.remove_categories, remove_categories: &bulk_edit_project.remove_categories,
), },
3, 3,
false, false,
&mut transaction, &mut transaction,
@@ -1256,11 +1259,12 @@ pub async fn projects_edit(
&categories, &categories,
&project.additional_categories, &project.additional_categories,
project.inner.id as db_ids::ProjectId, project.inner.id as db_ids::ProjectId,
CategoryChanges::new( CategoryChanges {
&bulk_edit_project.additional_categories, categories: &bulk_edit_project.additional_categories,
&bulk_edit_project.add_additional_categories, add_categories: &bulk_edit_project.add_additional_categories,
&bulk_edit_project.remove_additional_categories, remove_categories: &bulk_edit_project
), .remove_additional_categories,
},
256, 256,
true, true,
&mut transaction, &mut transaction,
@@ -1383,11 +1387,11 @@ pub async fn bulk_edit_project_categories(
)) ))
})? })?
.id; .id;
mod_categories.push(ModCategory::new( mod_categories.push(ModCategory {
project_id, project_id,
category_id, category_id,
is_additional, is_additional,
)); });
} }
ModCategory::insert_many(mod_categories, &mut *transaction).await?; ModCategory::insert_many(mod_categories, &mut *transaction).await?;
} }

View File

@@ -1,8 +1,6 @@
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use actix_web::{HttpRequest, HttpResponse, web}; use actix_web::{HttpRequest, HttpResponse, web};
use lazy_static::lazy_static;
use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::PgPool; use sqlx::PgPool;
use validator::Validate; use validator::Validate;
@@ -358,13 +356,9 @@ pub async fn orgs_list(
} }
} }
lazy_static! {
static ref RE_URL_SAFE: Regex = Regex::new(r"^[a-zA-Z0-9_-]*$").unwrap();
}
#[derive(Serialize, Deserialize, Validate)] #[derive(Serialize, Deserialize, Validate)]
pub struct EditUser { pub struct EditUser {
#[validate(length(min = 1, max = 39), regex(path = *RE_URL_SAFE))] #[validate(length(min = 1, max = 39), regex(path = *crate::util::validate::RE_URL_SAFE))]
pub username: Option<String>, pub username: Option<String>,
#[serde( #[serde(
default, default,

View File

@@ -287,10 +287,10 @@ pub async fn version_edit_helper(
ApiError::Validation(validation_errors_to_string(err, None)) ApiError::Validation(validation_errors_to_string(err, None))
})?; })?;
let version_id = info.0; let version_id = info.0.into();
let id = version_id.into();
let result = database::models::Version::get(id, &**pool, &redis).await?; let result =
database::models::Version::get(version_id, &**pool, &redis).await?;
if let Some(version_item) = result { if let Some(version_item) = result {
let team_member = let team_member =
@@ -345,7 +345,7 @@ pub async fn version_edit_helper(
WHERE (id = $2) WHERE (id = $2)
", ",
name.trim(), name.trim(),
id as database::models::ids::VersionId, version_id as database::models::ids::VersionId,
) )
.execute(&mut *transaction) .execute(&mut *transaction)
.await?; .await?;
@@ -359,7 +359,7 @@ pub async fn version_edit_helper(
WHERE (id = $2) WHERE (id = $2)
", ",
number, number,
id as database::models::ids::VersionId, version_id as database::models::ids::VersionId,
) )
.execute(&mut *transaction) .execute(&mut *transaction)
.await?; .await?;
@@ -373,7 +373,7 @@ pub async fn version_edit_helper(
WHERE (id = $2) WHERE (id = $2)
", ",
version_type.as_str(), version_type.as_str(),
id as database::models::ids::VersionId, version_id as database::models::ids::VersionId,
) )
.execute(&mut *transaction) .execute(&mut *transaction)
.await?; .await?;
@@ -384,7 +384,7 @@ pub async fn version_edit_helper(
" "
DELETE FROM dependencies WHERE dependent_id = $1 DELETE FROM dependencies WHERE dependent_id = $1
", ",
id as database::models::ids::VersionId, version_id as database::models::ids::VersionId,
) )
.execute(&mut *transaction) .execute(&mut *transaction)
.await?; .await?;
@@ -448,7 +448,7 @@ pub async fn version_edit_helper(
WHERE version_id = $1 WHERE version_id = $1
AND field_id = ANY($2) AND field_id = ANY($2)
", ",
id as database::models::ids::VersionId, version_id as database::models::ids::VersionId,
&loader_field_ids &loader_field_ids
) )
.execute(&mut *transaction) .execute(&mut *transaction)
@@ -476,7 +476,7 @@ pub async fn version_edit_helper(
.remove(&loader_field.id) .remove(&loader_field.id)
.unwrap_or_default(); .unwrap_or_default();
let vf: VersionField = VersionField::check_parse( let vf: VersionField = VersionField::check_parse(
version_id.into(), version_id,
loader_field.clone(), loader_field.clone(),
vf_value.clone(), vf_value.clone(),
enum_variants, enum_variants,
@@ -493,7 +493,7 @@ pub async fn version_edit_helper(
" "
DELETE FROM loaders_versions WHERE version_id = $1 DELETE FROM loaders_versions WHERE version_id = $1
", ",
id as database::models::ids::VersionId, version_id as database::models::ids::VersionId,
) )
.execute(&mut *transaction) .execute(&mut *transaction)
.await?; .await?;
@@ -513,7 +513,10 @@ pub async fn version_edit_helper(
.to_string(), .to_string(),
) )
})?; })?;
loader_versions.push(LoaderVersion::new(loader_id, id)); loader_versions.push(LoaderVersion {
loader_id,
version_id,
});
} }
LoaderVersion::insert_many(loader_versions, &mut transaction) LoaderVersion::insert_many(loader_versions, &mut transaction)
.await?; .await?;
@@ -535,7 +538,7 @@ pub async fn version_edit_helper(
WHERE (id = $2) WHERE (id = $2)
", ",
featured, featured,
id as database::models::ids::VersionId, version_id as database::models::ids::VersionId,
) )
.execute(&mut *transaction) .execute(&mut *transaction)
.await?; .await?;
@@ -549,7 +552,7 @@ pub async fn version_edit_helper(
WHERE (id = $2) WHERE (id = $2)
", ",
body, body,
id as database::models::ids::VersionId, version_id as database::models::ids::VersionId,
) )
.execute(&mut *transaction) .execute(&mut *transaction)
.await?; .await?;
@@ -569,7 +572,7 @@ pub async fn version_edit_helper(
WHERE (id = $2) WHERE (id = $2)
", ",
*downloads as i32, *downloads as i32,
id as database::models::ids::VersionId, version_id as database::models::ids::VersionId,
) )
.execute(&mut *transaction) .execute(&mut *transaction)
.await?; .await?;
@@ -604,7 +607,7 @@ pub async fn version_edit_helper(
WHERE (id = $2) WHERE (id = $2)
", ",
status.as_str(), status.as_str(),
id as database::models::ids::VersionId, version_id as database::models::ids::VersionId,
) )
.execute(&mut *transaction) .execute(&mut *transaction)
.await?; .await?;
@@ -652,7 +655,7 @@ pub async fn version_edit_helper(
WHERE (id = $2) WHERE (id = $2)
", ",
ordering.to_owned() as Option<i32>, ordering.to_owned() as Option<i32>,
id as database::models::ids::VersionId, version_id as database::models::ids::VersionId,
) )
.execute(&mut *transaction) .execute(&mut *transaction)
.await?; .await?;

View File

@@ -1,14 +1,13 @@
use std::sync::LazyLock;
use itertools::Itertools; use itertools::Itertools;
use lazy_static::lazy_static;
use regex::Regex; use regex::Regex;
use validator::{ValidationErrors, ValidationErrorsKind}; use validator::{ValidationErrors, ValidationErrorsKind};
use crate::models::pats::Scopes; use crate::models::pats::Scopes;
lazy_static! { pub static RE_URL_SAFE: LazyLock<Regex> =
pub static ref RE_URL_SAFE: Regex = LazyLock::new(|| Regex::new(r"^[a-zA-Z0-9_-]*$").unwrap());
Regex::new(r#"^[a-zA-Z0-9!@$()`.+,_"-]*$"#).unwrap();
}
//TODO: In order to ensure readability, only the first error is printed, this may need to be expanded on in the future! //TODO: In order to ensure readability, only the first error is printed, this may need to be expanded on in the future!
pub fn validation_errors_to_string( pub fn validation_errors_to_string(

View File

@@ -5,75 +5,72 @@ authors = ["Jai A <jaiagr+gpg@pm.me>"]
edition = "2024" edition = "2024"
[dependencies] [dependencies]
bytes = "1" bytes.workspace = true
serde = { version = "1.0", features = ["derive"] } serde = { workspace = true, features = ["derive"] }
serde_json = "1.0" serde_json.workspace = true
serde_ini = "0.2.0" serde_ini.workspace = true
sha1_smol = { version = "1.0.0", features = ["std"] } sha1_smol.workspace = true
sha2 = "0.10.9" sha2.workspace = true
url = { version = "2.2", features = ["serde"] } url = { workspace = true, features = ["serde"] }
uuid = { version = "1.1", features = ["serde", "v4"] } uuid = { workspace = true, features = ["serde", "v4"] }
zip = "2.6.1" zip.workspace = true
async_zip = { version = "0.0.17", features = ["chrono", "tokio-fs", "deflate", "bzip2", "zstd", "deflate64"] } async_zip = { workspace = true, features = ["chrono", "tokio-fs", "deflate", "bzip2", "zstd", "deflate64"] }
flate2 = "1.1.1" flate2.workspace = true
tempfile = "3.5.0" tempfile.workspace = true
dashmap = { version = "6.1.0", features = ["serde"] } dashmap = { workspace = true, features = ["serde"] }
quick-xml = { version = "0.37", features = ["async-tokio"] } quick-xml = { workspace = true, features = ["async-tokio"] }
enumset = "1.1" enumset.workspace = true
chrono = { version = "0.4.19", features = ["serde"] } chrono = { workspace = true, features = ["serde"] }
daedalus = { path = "../../packages/daedalus" } daedalus.workspace = true
dirs = "6.0.0" dirs.workspace = true
regex = "1.5" regex.workspace = true
sys-info = "0.9.0" sysinfo = { workspace = true, features = ["system", "disk"] }
sysinfo = "0.35.0" thiserror.workspace = true
thiserror = "2.0.12" either.workspace = true
either = "1.13"
tracing = "0.1.37" tracing.workspace = true
tracing-subscriber = { version = "0.3.18", features = ["chrono", "env-filter"] } tracing-subscriber = { workspace = true, features = ["chrono", "env-filter"] }
tracing-error = "0.2.0" tracing-error.workspace = true
paste = { version = "1.0" } paste.workspace = true
tauri = { version = "2.5.1", optional = true } tauri = { workspace = true, optional = true }
indicatif = { version = "0.17.3", optional = true } indicatif = { workspace = true, optional = true }
async-tungstenite = { version = "0.29.1", features = ["tokio-runtime", "tokio-rustls-webpki-roots"] } async-tungstenite = { workspace = true, features = ["tokio-runtime", "tokio-rustls-webpki-roots"] }
futures = "0.3" futures = { workspace = true, features = ["async-await", "alloc"] }
reqwest = { version = "0.12.15", features = ["json", "stream", "deflate", "gzip", "brotli", "rustls-tls", "charset", "http2", "macos-system-configuration"], default-features = false } reqwest = { workspace = true, features = ["json", "stream", "deflate", "gzip", "brotli", "rustls-tls-webpki-roots", "charset", "http2", "macos-system-configuration"] }
tokio = { version = "1", features = ["full"] } tokio = { workspace = true, features = ["time", "io-util", "net", "sync", "fs", "macros", "process"] }
tokio-util = "0.7" tokio-util = { workspace = true, features = ["compat"] }
async-recursion = "1.0.4" async-recursion.workspace = true
fs4 = { version = "0.13", features = ["tokio"] } fs4 = { workspace = true, features = ["tokio"] }
async-walkdir = "2.1" async-walkdir.workspace = true
async-compression = { version = "0.4", default-features = false, features = ["tokio", "gzip"] } async-compression = { workspace = true, features = ["tokio", "gzip"] }
notify = { version = "8.0.0", default-features = false } notify.workspace = true
notify-debouncer-mini = { version = "0.6.0", default-features = false } notify-debouncer-mini.workspace = true
lazy_static = "1.4.0" dunce.workspace = true
dunce = "1.0.3"
whoami = "1.4.0" whoami.workspace = true
discord-rich-presence = "0.2.4" discord-rich-presence.workspace = true
p256 = { version = "0.13.2", features = ["ecdsa"] } p256 = { workspace = true, features = ["ecdsa"] }
rand = "0.8" rand.workspace = true
byteorder = "1.5.0" base64.workspace = true
base64 = "0.22.1"
sqlx = { version = "0.8.2", features = [ "runtime-tokio", "sqlite", "macros" ] } sqlx = { workspace = true, features = ["runtime-tokio", "sqlite", "macros", "migrate", "json"] }
quartz_nbt = { version = "0.2", features = ["serde"] } quartz_nbt = { workspace = true, features = ["serde"] }
hickory-resolver = "0.25" hickory-resolver.workspace = true
ariadne = { path = "../ariadne" } ariadne.workspace = true
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winreg = "0.55.0" winreg.workspace = true
[features] [features]
tauri = ["dep:tauri"] tauri = ["dep:tauri"]

View File

@@ -6,6 +6,7 @@ use dashmap::DashMap;
use reqwest::Method; use reqwest::Method;
use serde::Deserialize; use serde::Deserialize;
use std::path::PathBuf; use std::path::PathBuf;
use sysinfo::{MemoryRefreshKind, RefreshKind};
use crate::util::io; use crate::util::io;
use crate::util::jre::extract_java_majorminor_version; use crate::util::jre::extract_java_majorminor_version;
@@ -175,11 +176,10 @@ pub async fn test_jre(
// Gets maximum memory in KiB. // Gets maximum memory in KiB.
pub async fn get_max_memory() -> crate::Result<u64> { pub async fn get_max_memory() -> crate::Result<u64> {
Ok(sys_info::mem_info() Ok(sysinfo::System::new_with_specifics(
.map_err(|_| { RefreshKind::nothing()
crate::Error::from(crate::ErrorKind::LauncherError( .with_memory(MemoryRefreshKind::nothing().with_ram()),
"Unable to get computer memory".to_string(), )
)) .total_memory()
})? / 1024)
.total)
} }

View File

@@ -17,7 +17,6 @@ use either::Either;
use enumset::{EnumSet, EnumSetType}; use enumset::{EnumSet, EnumSetType};
use fs4::tokio::AsyncFileExt; use fs4::tokio::AsyncFileExt;
use futures::StreamExt; use futures::StreamExt;
use lazy_static::lazy_static;
use quartz_nbt::{NbtCompound, NbtTag}; use quartz_nbt::{NbtCompound, NbtTag};
use regex::{Regex, RegexBuilder}; use regex::{Regex, RegexBuilder};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -25,6 +24,7 @@ use std::cmp::Reverse;
use std::io::Cursor; use std::io::Cursor;
use std::net::{Ipv4Addr, Ipv6Addr}; use std::net::{Ipv4Addr, Ipv6Addr};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::LazyLock;
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use tokio_util::compat::FuturesAsyncWriteCompatExt; use tokio_util::compat::FuturesAsyncWriteCompatExt;
use url::Url; use url::Url;
@@ -548,17 +548,19 @@ pub async fn backup_world(instance: &Path, world: &str) -> Result<u64> {
} }
fn find_available_name(dir: &Path, file_name: &str, extension: &str) -> String { fn find_available_name(dir: &Path, file_name: &str, extension: &str) -> String {
lazy_static! { static RESERVED_WINDOWS_FILENAMES: LazyLock<Regex> = LazyLock::new(|| {
static ref RESERVED_WINDOWS_FILENAMES: Regex = RegexBuilder::new(r#"^.*\.|(?:COM|CLOCK\$|CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\..*)?$"#) RegexBuilder::new(r#"^.*\.|(?:COM|CLOCK\$|CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\..*)?$"#)
.case_insensitive(true) .case_insensitive(true)
.build() .build()
.unwrap(); .unwrap()
static ref COPY_COUNTER_PATTERN: Regex = RegexBuilder::new(r#"^(?<name>.*) \((?<count>\d*)\)$"#) });
static COPY_COUNTER_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
RegexBuilder::new(r#"^(?<name>.*) \((?<count>\d*)\)$"#)
.case_insensitive(true) .case_insensitive(true)
.unicode(true) .unicode(true)
.build() .build()
.unwrap(); .unwrap()
} });
let mut file_name = file_name.replace( let mut file_name = file_name.replace(
[ [

View File

@@ -5,30 +5,27 @@ use crate::state::{
DirectoryInfo, ProfileInstallStage, ProjectType, attached_world_data, DirectoryInfo, ProfileInstallStage, ProjectType, attached_world_data,
}; };
use crate::worlds::WorldType; use crate::worlds::WorldType;
use futures::{SinkExt, StreamExt, channel::mpsc::channel};
use notify::{RecommendedWatcher, RecursiveMode}; use notify::{RecommendedWatcher, RecursiveMode};
use notify_debouncer_mini::{DebounceEventResult, Debouncer, new_debouncer}; use notify_debouncer_mini::{DebounceEventResult, Debouncer, new_debouncer};
use std::time::Duration; use std::time::Duration;
use tokio::sync::RwLock; use tokio::sync::{RwLock, mpsc::channel};
pub type FileWatcher = RwLock<Debouncer<RecommendedWatcher>>; pub type FileWatcher = RwLock<Debouncer<RecommendedWatcher>>;
pub async fn init_watcher() -> crate::Result<FileWatcher> { pub async fn init_watcher() -> crate::Result<FileWatcher> {
let (mut tx, mut rx) = channel(1); let (tx, mut rx) = channel(1);
let file_watcher = new_debouncer( let file_watcher = new_debouncer(
Duration::from_secs_f32(1.0), Duration::from_secs_f32(1.0),
move |res: DebounceEventResult| { move |res: DebounceEventResult| {
futures::executor::block_on(async { tx.blocking_send(res).ok();
tx.send(res).await.unwrap();
})
}, },
)?; )?;
tokio::task::spawn(async move { tokio::task::spawn(async move {
let span = tracing::span!(tracing::Level::INFO, "init_watcher"); let span = tracing::span!(tracing::Level::INFO, "init_watcher");
tracing::info!(parent: &span, "Initting watcher"); tracing::info!(parent: &span, "Initting watcher");
while let Some(res) = rx.next().await { while let Some(res) = rx.recv().await {
let _span = span.enter(); let _span = span.enter();
match res { match res {

View File

@@ -2,7 +2,6 @@ use crate::ErrorKind;
use crate::util::fetch::REQWEST_CLIENT; use crate::util::fetch::REQWEST_CLIENT;
use base64::Engine; use base64::Engine;
use base64::prelude::{BASE64_STANDARD, BASE64_URL_SAFE_NO_PAD}; use base64::prelude::{BASE64_STANDARD, BASE64_URL_SAFE_NO_PAD};
use byteorder::BigEndian;
use chrono::{DateTime, Duration, TimeZone, Utc}; use chrono::{DateTime, Duration, TimeZone, Utc};
use dashmap::DashMap; use dashmap::DashMap;
use futures::TryStreamExt; use futures::TryStreamExt;
@@ -62,12 +61,6 @@ pub enum MinecraftAuthenticationError {
#[source] #[source]
source: reqwest::Error, source: reqwest::Error,
}, },
#[error("Error creating signed request buffer {step:?}: {source}")]
ConstructingSignedRequest {
step: MinecraftAuthStep,
#[source]
source: std::io::Error,
},
#[error("Error reading XBOX Session ID header")] #[error("Error reading XBOX Session ID header")]
NoSessionId, NoSessionId,
#[error("Error reading user hash")] #[error("Error reading user hash")]
@@ -1087,56 +1080,25 @@ async fn send_signed_request<T: DeserializeOwned>(
let time: u128 = let time: u128 =
{ ((current_date.timestamp() as u128) + 11644473600) * 10000000 }; { ((current_date.timestamp() as u128) + 11644473600) * 10000000 };
use byteorder::WriteBytesExt;
let mut buffer = Vec::new(); let mut buffer = Vec::new();
buffer.write_u32::<BigEndian>(1).map_err(|source| { buffer.extend_from_slice(&1_u32.to_be_bytes()[..]);
MinecraftAuthenticationError::ConstructingSignedRequest { source, step } buffer.push(0_u8);
})?; buffer.extend_from_slice(&(time as u64).to_be_bytes()[..]);
buffer.write_u8(0).map_err(|source| { buffer.push(0_u8);
MinecraftAuthenticationError::ConstructingSignedRequest { source, step }
})?;
buffer
.write_u64::<BigEndian>(time as u64)
.map_err(|source| {
MinecraftAuthenticationError::ConstructingSignedRequest {
source,
step,
}
})?;
buffer.write_u8(0).map_err(|source| {
MinecraftAuthenticationError::ConstructingSignedRequest { source, step }
})?;
buffer.extend_from_slice("POST".as_bytes()); buffer.extend_from_slice("POST".as_bytes());
buffer.write_u8(0).map_err(|source| { buffer.push(0_u8);
MinecraftAuthenticationError::ConstructingSignedRequest { source, step }
})?;
buffer.extend_from_slice(url_path.as_bytes()); buffer.extend_from_slice(url_path.as_bytes());
buffer.write_u8(0).map_err(|source| { buffer.push(0_u8);
MinecraftAuthenticationError::ConstructingSignedRequest { source, step }
})?;
buffer.extend_from_slice(&auth); buffer.extend_from_slice(&auth);
buffer.write_u8(0).map_err(|source| { buffer.push(0_u8);
MinecraftAuthenticationError::ConstructingSignedRequest { source, step }
})?;
buffer.extend_from_slice(&body); buffer.extend_from_slice(&body);
buffer.write_u8(0).map_err(|source| { buffer.push(0_u8);
MinecraftAuthenticationError::ConstructingSignedRequest { source, step }
})?;
let ecdsa_sig: Signature = key.key.sign(&buffer); let ecdsa_sig: Signature = key.key.sign(&buffer);
let mut sig_buffer = Vec::new(); let mut sig_buffer = Vec::new();
sig_buffer.write_i32::<BigEndian>(1).map_err(|source| { sig_buffer.extend_from_slice(&1_i32.to_be_bytes()[..]);
MinecraftAuthenticationError::ConstructingSignedRequest { source, step } sig_buffer.extend_from_slice(&(time as u64).to_be_bytes()[..]);
})?;
sig_buffer
.write_u64::<BigEndian>(time as u64)
.map_err(|source| {
MinecraftAuthenticationError::ConstructingSignedRequest {
source,
step,
}
})?;
sig_buffer.extend_from_slice(&ecdsa_sig.r().to_bytes()); sig_buffer.extend_from_slice(&ecdsa_sig.r().to_bytes());
sig_buffer.extend_from_slice(&ecdsa_sig.s().to_bytes()); sig_buffer.extend_from_slice(&ecdsa_sig.s().to_bytes());

View File

@@ -9,7 +9,6 @@ use crate::util::fetch::{FetchSemaphore, IoSemaphore, write_cached_icon};
use crate::util::io::{self}; use crate::util::io::{self};
use chrono::{DateTime, TimeDelta, TimeZone, Utc}; use chrono::{DateTime, TimeDelta, TimeZone, Utc};
use dashmap::DashMap; use dashmap::DashMap;
use lazy_static::lazy_static;
use regex::Regex; use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::SqlitePool; use sqlx::SqlitePool;
@@ -17,6 +16,7 @@ use std::collections::HashSet;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::convert::TryInto; use std::convert::TryInto;
use std::path::Path; use std::path::Path;
use std::sync::LazyLock;
use tokio::fs::DirEntry; use tokio::fs::DirEntry;
use tokio::io::{AsyncBufReadExt, AsyncRead}; use tokio::io::{AsyncBufReadExt, AsyncRead};
use tokio::task::JoinSet; use tokio::task::JoinSet;
@@ -837,9 +837,9 @@ impl Profile {
state: &crate::State, state: &crate::State,
join_entry: &mut JoinLogEntry, join_entry: &mut JoinLogEntry,
) -> crate::Result<()> { ) -> crate::Result<()> {
lazy_static! { static LOG_LINE_REGEX: LazyLock<Regex> = LazyLock::new(|| {
static ref LOG_LINE_REGEX: Regex = Regex::new(r"^\[[0-9]{2}(?::[0-9]{2}){2}] \[.+?/[A-Z]+?]: Connecting to (.+?), ([1-9][0-9]{0,4})$").unwrap(); Regex::new(r"^\[[0-9]{2}(?::[0-9]{2}){2}] \[.+?/[A-Z]+?]: Connecting to (.+?), ([1-9][0-9]{0,4})$").unwrap()
} });
let reader = tokio::io::BufReader::new(reader); let reader = tokio::io::BufReader::new(reader);
let mut lines = reader.lines(); let mut lines = reader.lines();
while let Some(log_line) = lines.next_line().await? { while let Some(log_line) = lines.next_line().await? {

View File

@@ -1,14 +1,14 @@
//! Functions for fetching infromation from the Internet //! Functions for fetching information from the Internet
use super::io::{self, IOError}; use super::io::{self, IOError};
use crate::config::{MODRINTH_API_URL, MODRINTH_API_URL_V3}; use crate::config::{MODRINTH_API_URL, MODRINTH_API_URL_V3};
use crate::event::LoadingBarId; use crate::event::LoadingBarId;
use crate::event::emit::emit_loading; use crate::event::emit::emit_loading;
use bytes::Bytes; use bytes::Bytes;
use lazy_static::lazy_static;
use reqwest::Method; use reqwest::Method;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::LazyLock;
use std::time::{self}; use std::time::{self};
use tokio::sync::Semaphore; use tokio::sync::Semaphore;
use tokio::{fs::File, io::AsyncWriteExt}; use tokio::{fs::File, io::AsyncWriteExt};
@@ -18,22 +18,20 @@ pub struct IoSemaphore(pub Semaphore);
#[derive(Debug)] #[derive(Debug)]
pub struct FetchSemaphore(pub Semaphore); pub struct FetchSemaphore(pub Semaphore);
lazy_static! { pub static REQWEST_CLIENT: LazyLock<reqwest::Client> = LazyLock::new(|| {
pub static ref REQWEST_CLIENT: reqwest::Client = { let mut headers = reqwest::header::HeaderMap::new();
let mut headers = reqwest::header::HeaderMap::new(); let header = reqwest::header::HeaderValue::from_str(&format!(
let header = reqwest::header::HeaderValue::from_str(&format!( "modrinth/theseus/{} (support@modrinth.com)",
"modrinth/theseus/{} (support@modrinth.com)", env!("CARGO_PKG_VERSION")
env!("CARGO_PKG_VERSION") ))
)) .unwrap();
.unwrap(); headers.insert(reqwest::header::USER_AGENT, header);
headers.insert(reqwest::header::USER_AGENT, header); reqwest::Client::builder()
reqwest::Client::builder() .tcp_keepalive(Some(time::Duration::from_secs(10)))
.tcp_keepalive(Some(time::Duration::from_secs(10))) .default_headers(headers)
.default_headers(headers) .build()
.build() .expect("Reqwest Client Building Failed")
.expect("Reqwest Client Building Failed") });
};
}
const FETCH_ATTEMPTS: usize = 3; const FETCH_ATTEMPTS: usize = 3;
#[tracing::instrument(skip(semaphore))] #[tracing::instrument(skip(semaphore))]

View File

@@ -276,11 +276,10 @@ pub async fn check_java_at_filepath(path: &Path) -> Option<JavaVersion> {
}; };
let bytes = include_bytes!("../../library/JavaInfo.class"); let bytes = include_bytes!("../../library/JavaInfo.class");
let tempdir: PathBuf = tempfile::tempdir().ok()?.into_path(); let Ok(tempdir) = tempfile::tempdir() else {
if !tempdir.exists() {
return None; return None;
} };
let file_path = tempdir.join("JavaInfo.class"); let file_path = tempdir.path().join("JavaInfo.class");
io::write(&file_path, bytes).await.ok()?; io::write(&file_path, bytes).await.ok()?;
let output = Command::new(&java) let output = Command::new(&java)

View File

@@ -1,6 +1,5 @@
//! Platform-related code //! Platform-related code
use daedalus::minecraft::{Os, OsRule}; use daedalus::minecraft::{Os, OsRule};
use regex::Regex;
// OS detection // OS detection
pub trait OsExt { pub trait OsExt {
@@ -92,12 +91,16 @@ pub fn os_rule(
} }
} }
if let Some(version) = &rule.version { // `rule.version` is ignored because it's not usually seen on real recent
if let Ok(regex) = Regex::new(version.as_str()) { // Minecraft version manifests, its alleged regex syntax is undefined and is
rule_match &= // likely to not match `Regex`'s, and the way to get the value to match it
regex.is_match(&sys_info::os_release().unwrap_or_default()); // against is allegedly calling `System.getProperty("os.version")`, which
} // on Windows the OpenJDK implements by fetching the kernel32.dll version,
} // an approach that no public Rust library implements. Moreover, launchers
// such as PrismLauncher also ignore this field. Code references:
// - https://github.com/openjdk/jdk/blob/948ade8e7003a41683600428c8e3155c7ed798db/src/java.base/windows/native/libjava/java_props_md.c#L556
// - https://github.com/PrismLauncher/PrismLauncher/blob/1c20faccf88999474af70db098a4c10e7a03af33/launcher/minecraft/Rule.h#L77
// - https://github.com/FillZpp/sys-info-rs/blob/60ecf1470a5b7c90242f429934a3bacb6023ec4d/c/windows.c#L23-L38
rule_match rule_match
} }

View File

@@ -4,13 +4,12 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
serde = { version = "1.0", features = ["derive"] } serde = { workspace = true, features = ["derive"] }
serde_json = "1.0" serde_json.workspace = true
thiserror = "2.0.12" thiserror.workspace = true
uuid = { version = "1.2.2", features = ["v4", "fast-rng", "serde"] } uuid = { workspace = true, features = ["v4", "fast-rng", "serde"] }
serde_bytes = "0.11" serde_bytes.workspace = true
rand = "0.8.5" rand.workspace = true
either = "1.13" either.workspace = true
chrono = { version = "0.4.26", features = ["serde"] } chrono = { workspace = true, features = ["serde"] }
serde_cbor = "0.11" serde_cbor.workspace = true
lazy_static = "1.5"

View File

@@ -1,8 +1,7 @@
use lazy_static::lazy_static; use std::{collections::HashMap, sync::LazyLock};
use std::collections::HashMap;
lazy_static! { static SPECIAL_PARENTS: LazyLock<HashMap<&'static str, &'static str>> =
static ref SPECIAL_PARENTS: HashMap<&'static str, &'static str> = { LazyLock::new(|| {
let mut m = HashMap::new(); let mut m = HashMap::new();
m.insert("15w14a", "1.8.3"); m.insert("15w14a", "1.8.3");
m.insert("1.RV-Pre1", "1.9.2"); m.insert("1.RV-Pre1", "1.9.2");
@@ -12,8 +11,7 @@ lazy_static! {
m.insert("23w13a_or_b", "23w13a"); m.insert("23w13a_or_b", "23w13a");
m.insert("24w14potato", "24w12a"); m.insert("24w14potato", "24w12a");
m m
}; });
}
pub fn is_feature_supported_in( pub fn is_feature_supported_in(
version: &str, version: &str,

View File

@@ -14,7 +14,7 @@ readme = "README.md"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
serde = { version = "1.0", features = ["derive"] } serde = { workspace = true, features = ["derive"] }
serde_json = "1.0" serde_json.workspace = true
chrono = { version = "0.4", features = ["serde"] } chrono = { workspace = true, features = ["serde"] }
thiserror = "2.0" thiserror.workspace = true