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
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
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