You've already forked AstralRinth
forked from didirus/AstralRinth
Mod Management API (#81)
* Profile mod management * remove print statement
This commit is contained in:
36
Cargo.lock
generated
36
Cargo.lock
generated
@@ -777,12 +777,6 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "diff"
|
|
||||||
version = "0.1.13"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@@ -2210,15 +2204,6 @@ dependencies = [
|
|||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "output_vt100"
|
|
||||||
version = "0.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66"
|
|
||||||
dependencies = [
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "overload"
|
name = "overload"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -2511,18 +2496,6 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pretty_assertions"
|
|
||||||
version = "1.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755"
|
|
||||||
dependencies = [
|
|
||||||
"ctor",
|
|
||||||
"diff",
|
|
||||||
"output_vt100",
|
|
||||||
"yansi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-crate"
|
name = "proc-macro-crate"
|
||||||
version = "1.3.1"
|
version = "1.3.1"
|
||||||
@@ -3553,7 +3526,6 @@ dependencies = [
|
|||||||
name = "theseus"
|
name = "theseus"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"argh",
|
|
||||||
"async-tungstenite",
|
"async-tungstenite",
|
||||||
"async_zip",
|
"async_zip",
|
||||||
"bincode",
|
"bincode",
|
||||||
@@ -3565,8 +3537,6 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
|
||||||
"pretty_assertions",
|
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -4621,12 +4591,6 @@ dependencies = [
|
|||||||
"lzma-sys",
|
"lzma-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "yansi"
|
|
||||||
version = "0.5.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zeroize"
|
name = "zeroize"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ tracing-error = "0.2"
|
|||||||
|
|
||||||
async-tungstenite = { version = "0.20.0", features = ["tokio-runtime", "tokio-native-tls"] }
|
async-tungstenite = { version = "0.20.0", features = ["tokio-runtime", "tokio-native-tls"] }
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
once_cell = "1.9.0"
|
|
||||||
reqwest = { version = "0.11", features = ["json"] }
|
reqwest = { version = "0.11", features = ["json"] }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
tokio-stream = { version = "0.1", features = ["fs"] }
|
tokio-stream = { version = "0.1", features = ["fs"] }
|
||||||
@@ -45,8 +44,4 @@ dunce = "1.0.3"
|
|||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winreg = "0.11.0"
|
winreg = "0.11.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
argh = "0.1.6"
|
|
||||||
pretty_assertions = "1.1.0"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ pub async fn authenticate(
|
|||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let credentials = flow.extract_credentials().await?;
|
let credentials = flow.extract_credentials(&state.io_semaphore).await?;
|
||||||
users.insert(&credentials)?;
|
users.insert(&credentials)?;
|
||||||
|
|
||||||
if state.settings.read().await.default_user.is_none() {
|
if state.settings.read().await.default_user.is_none() {
|
||||||
@@ -60,13 +60,11 @@ pub async fn authenticate(
|
|||||||
/// Refresh some credentials using Hydra, if needed
|
/// Refresh some credentials using Hydra, if needed
|
||||||
/// This is the primary desired way to get credentials, as it will also refresh them.
|
/// This is the primary desired way to get credentials, as it will also refresh them.
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub async fn refresh(
|
pub async fn refresh(user: uuid::Uuid) -> crate::Result<Credentials> {
|
||||||
user: uuid::Uuid,
|
|
||||||
update_name: bool,
|
|
||||||
) -> crate::Result<Credentials> {
|
|
||||||
let state = State::get().await?;
|
let state = State::get().await?;
|
||||||
let mut users = state.users.write().await;
|
let mut users = state.users.write().await;
|
||||||
|
|
||||||
|
let io_sempahore = &state.io_semaphore;
|
||||||
futures::future::ready(users.get(user)?.ok_or_else(|| {
|
futures::future::ready(users.get(user)?.ok_or_else(|| {
|
||||||
crate::ErrorKind::OtherError(format!(
|
crate::ErrorKind::OtherError(format!(
|
||||||
"Tried to refresh nonexistent user with ID {user}"
|
"Tried to refresh nonexistent user with ID {user}"
|
||||||
@@ -75,10 +73,7 @@ pub async fn refresh(
|
|||||||
}))
|
}))
|
||||||
.and_then(|mut credentials| async move {
|
.and_then(|mut credentials| async move {
|
||||||
if chrono::offset::Utc::now() > credentials.expires {
|
if chrono::offset::Utc::now() > credentials.expires {
|
||||||
inner::refresh_credentials(&mut credentials).await?;
|
inner::refresh_credentials(&mut credentials, io_sempahore).await?;
|
||||||
if update_name {
|
|
||||||
inner::refresh_username(&mut credentials).await?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
users.insert(&credentials)?;
|
users.insert(&credentials)?;
|
||||||
Ok(credentials)
|
Ok(credentials)
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ pub async fn install_pack_from_version_id(
|
|||||||
Method::GET,
|
Method::GET,
|
||||||
&format!("{}version/{}", MODRINTH_API_URL, version_id),
|
&format!("{}version/{}", MODRINTH_API_URL, version_id),
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
&state.io_semaphore,
|
&state.io_semaphore,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -104,6 +105,7 @@ pub async fn install_pack_from_version_id(
|
|||||||
Method::GET,
|
Method::GET,
|
||||||
&format!("{}project/{}", MODRINTH_API_URL, version.project_id),
|
&format!("{}project/{}", MODRINTH_API_URL, version.project_id),
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
&state.io_semaphore,
|
&state.io_semaphore,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -230,7 +232,7 @@ async fn install_pack(
|
|||||||
let profile = profile.clone();
|
let profile = profile.clone();
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
// TODO: Future update: prompt user for optional files in a modpack
|
//TODO: Future update: prompt user for optional files in a modpack
|
||||||
if let Some(env) = project.env {
|
if let Some(env) = project.env {
|
||||||
if env
|
if env
|
||||||
.get(&EnvType::Client)
|
.get(&EnvType::Client)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use std::{
|
|||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use tokio::{process::Command, sync::RwLock};
|
use tokio::{fs, process::Command, sync::RwLock};
|
||||||
|
|
||||||
/// Remove a profile
|
/// Remove a profile
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
@@ -67,23 +67,10 @@ pub async fn list() -> crate::Result<std::collections::HashMap<PathBuf, Profile>
|
|||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub async fn sync(path: &Path) -> crate::Result<()> {
|
pub async fn sync(path: &Path) -> crate::Result<()> {
|
||||||
let state = State::get().await?;
|
let state = State::get().await?;
|
||||||
|
let mut profiles = state.profiles.write().await;
|
||||||
|
|
||||||
if let Some(profile) = get(path).await? {
|
if let Some(profile) = profiles.0.get_mut(path) {
|
||||||
let paths = profile.get_profile_project_paths()?;
|
profile.sync().await?;
|
||||||
let projects = crate::state::infer_data_from_files(
|
|
||||||
paths,
|
|
||||||
state.directories.caches_dir(),
|
|
||||||
&state.io_semaphore,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut profiles = state.profiles.write().await;
|
|
||||||
if let Some(profile) = profiles.0.get_mut(path) {
|
|
||||||
profile.projects = projects;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
State::sync().await?;
|
State::sync().await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -95,6 +82,99 @@ pub async fn sync(path: &Path) -> crate::Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a project from a version
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn add_project_from_version(
|
||||||
|
profile: &Path,
|
||||||
|
version_id: String,
|
||||||
|
) -> crate::Result<PathBuf> {
|
||||||
|
let state = State::get().await?;
|
||||||
|
let mut profiles = state.profiles.write().await;
|
||||||
|
|
||||||
|
if let Some(profile) = profiles.0.get_mut(profile) {
|
||||||
|
profile.add_project_version(version_id).await
|
||||||
|
} else {
|
||||||
|
Err(crate::ErrorKind::UnmanagedProfileError(
|
||||||
|
profile.display().to_string(),
|
||||||
|
)
|
||||||
|
.as_error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a project from an FS path
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn add_project_from_path(
|
||||||
|
profile: &Path,
|
||||||
|
path: &Path,
|
||||||
|
project_type: Option<String>,
|
||||||
|
) -> crate::Result<PathBuf> {
|
||||||
|
let state = State::get().await?;
|
||||||
|
let mut profiles = state.profiles.write().await;
|
||||||
|
|
||||||
|
if let Some(profile) = profiles.0.get_mut(profile) {
|
||||||
|
let file = fs::read(path).await?;
|
||||||
|
let file_name = path
|
||||||
|
.file_name()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
profile
|
||||||
|
.add_project_bytes(
|
||||||
|
&file_name,
|
||||||
|
bytes::Bytes::from(file),
|
||||||
|
project_type.and_then(|x| serde_json::from_str(&x).ok()),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
} else {
|
||||||
|
Err(crate::ErrorKind::UnmanagedProfileError(
|
||||||
|
profile.display().to_string(),
|
||||||
|
)
|
||||||
|
.as_error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Toggle whether a project is disabled or not
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn toggle_disable_project(
|
||||||
|
profile: &Path,
|
||||||
|
project: &Path,
|
||||||
|
) -> crate::Result<()> {
|
||||||
|
let state = State::get().await?;
|
||||||
|
let mut profiles = state.profiles.write().await;
|
||||||
|
|
||||||
|
if let Some(profile) = profiles.0.get_mut(profile) {
|
||||||
|
profile.toggle_disable_project(project).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(crate::ErrorKind::UnmanagedProfileError(
|
||||||
|
profile.display().to_string(),
|
||||||
|
)
|
||||||
|
.as_error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a project from a profile
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn remove_project(
|
||||||
|
profile: &Path,
|
||||||
|
project: &Path,
|
||||||
|
) -> crate::Result<()> {
|
||||||
|
let state = State::get().await?;
|
||||||
|
let mut profiles = state.profiles.write().await;
|
||||||
|
|
||||||
|
if let Some(profile) = profiles.0.get_mut(profile) {
|
||||||
|
profile.remove_project(project).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(crate::ErrorKind::UnmanagedProfileError(
|
||||||
|
profile.display().to_string(),
|
||||||
|
)
|
||||||
|
.as_error())
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Run Minecraft using a profile and the default credentials, logged in credentials,
|
/// Run Minecraft using a profile and the default credentials, logged in credentials,
|
||||||
/// failing with an error if no credentials are available
|
/// failing with an error if no credentials are available
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
@@ -104,13 +184,13 @@ pub async fn run(path: &Path) -> crate::Result<Arc<RwLock<MinecraftChild>>> {
|
|||||||
// Get default account and refresh credentials (preferred way to log in)
|
// Get default account and refresh credentials (preferred way to log in)
|
||||||
let default_account = state.settings.read().await.default_user;
|
let default_account = state.settings.read().await.default_user;
|
||||||
let credentials = if let Some(default_account) = default_account {
|
let credentials = if let Some(default_account) = default_account {
|
||||||
refresh(default_account, false).await?
|
refresh(default_account).await?
|
||||||
} else {
|
} else {
|
||||||
// If no default account, try to use a logged in account
|
// If no default account, try to use a logged in account
|
||||||
let users = auth::users().await?;
|
let users = auth::users().await?;
|
||||||
let last_account = users.iter().next();
|
let last_account = users.iter().next();
|
||||||
if let Some(last_account) = last_account {
|
if let Some(last_account) = last_account {
|
||||||
refresh(last_account.id, false).await?
|
refresh(last_account.id).await?
|
||||||
} else {
|
} else {
|
||||||
return Err(crate::ErrorKind::NoCredentialsError.as_error());
|
return Err(crate::ErrorKind::NoCredentialsError.as_error());
|
||||||
}
|
}
|
||||||
@@ -123,7 +203,7 @@ pub async fn run(path: &Path) -> crate::Result<Arc<RwLock<MinecraftChild>>> {
|
|||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn run_credentials(
|
pub async fn run_credentials(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
credentials: &crate::auth::Credentials,
|
credentials: &auth::Credentials,
|
||||||
) -> crate::Result<Arc<RwLock<MinecraftChild>>> {
|
) -> crate::Result<Arc<RwLock<MinecraftChild>>> {
|
||||||
let state = State::get().await?;
|
let state = State::get().await?;
|
||||||
let settings = state.settings.read().await;
|
let settings = state.settings.read().await;
|
||||||
|
|||||||
@@ -159,10 +159,10 @@ pub async fn profile_create(
|
|||||||
let settings = state.settings.read().await;
|
let settings = state.settings.read().await;
|
||||||
let optimal_version_key = jre::get_optimal_jre_key(&profile).await?;
|
let optimal_version_key = jre::get_optimal_jre_key(&profile).await?;
|
||||||
if settings.java_globals.get(&optimal_version_key).is_some() {
|
if settings.java_globals.get(&optimal_version_key).is_some() {
|
||||||
profile.set_java_settings(Some(JavaSettings {
|
profile.java = Some(JavaSettings {
|
||||||
jre_key: Some(optimal_version_key),
|
jre_key: Some(optimal_version_key),
|
||||||
extra_arguments: None,
|
extra_arguments: None,
|
||||||
}))?;
|
});
|
||||||
} else {
|
} else {
|
||||||
println!("Could not detect optimal JRE: {optimal_version_key}, falling back to system default.");
|
println!("Could not detect optimal JRE: {optimal_version_key}, falling back to system default.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,13 @@
|
|||||||
//! Configuration structs
|
//! Configuration structs
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use lazy_static::lazy_static;
|
||||||
use std::time;
|
|
||||||
|
|
||||||
pub static BINCODE_CONFIG: Lazy<bincode::config::Configuration> =
|
lazy_static! {
|
||||||
Lazy::new(|| {
|
pub static ref BINCODE_CONFIG: bincode::config::Configuration =
|
||||||
bincode::config::standard()
|
bincode::config::standard()
|
||||||
.with_little_endian()
|
.with_little_endian()
|
||||||
.with_no_limit()
|
.with_no_limit();
|
||||||
});
|
}
|
||||||
|
|
||||||
pub static REQWEST_CLIENT: Lazy<reqwest::Client> = Lazy::new(|| {
|
|
||||||
let mut headers = reqwest::header::HeaderMap::new();
|
|
||||||
let header = reqwest::header::HeaderValue::from_str(&format!(
|
|
||||||
"modrinth/daedalus/{} (support@modrinth.com)",
|
|
||||||
env!("CARGO_PKG_VERSION")
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
headers.insert(reqwest::header::USER_AGENT, header);
|
|
||||||
reqwest::Client::builder()
|
|
||||||
.tcp_keepalive(Some(time::Duration::from_secs(10)))
|
|
||||||
.default_headers(headers)
|
|
||||||
.build()
|
|
||||||
.expect("Reqwest Client Building Failed")
|
|
||||||
});
|
|
||||||
|
|
||||||
pub const MODRINTH_API_URL: &str = "https://api.modrinth.com/v2/";
|
pub const MODRINTH_API_URL: &str = "https://api.modrinth.com/v2/";
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
//! Authentication flow based on Hydra
|
//! Authentication flow based on Hydra
|
||||||
|
use crate::util::fetch::{fetch_advanced, fetch_json};
|
||||||
use async_tungstenite as ws;
|
use async_tungstenite as ws;
|
||||||
use bincode::{Decode, Encode};
|
use bincode::{Decode, Encode};
|
||||||
use chrono::{prelude::*, Duration};
|
use chrono::{prelude::*, Duration};
|
||||||
use futures::prelude::*;
|
use futures::prelude::*;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
use reqwest::Method;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::sync::{RwLock, Semaphore};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
@@ -93,7 +96,10 @@ impl HydraAuthFlow<ws::tokio::ConnectStream> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn extract_credentials(&mut self) -> crate::Result<Credentials> {
|
pub async fn extract_credentials(
|
||||||
|
&mut self,
|
||||||
|
semaphore: &RwLock<Semaphore>,
|
||||||
|
) -> crate::Result<Credentials> {
|
||||||
// Minecraft bearer token
|
// Minecraft bearer token
|
||||||
let token_resp = self
|
let token_resp = self
|
||||||
.socket
|
.socket
|
||||||
@@ -111,7 +117,7 @@ impl HydraAuthFlow<ws::tokio::ConnectStream> {
|
|||||||
Utc::now() + Duration::seconds(token.expires_after.into());
|
Utc::now() + Duration::seconds(token.expires_after.into());
|
||||||
|
|
||||||
// Get account credentials
|
// Get account credentials
|
||||||
let info = fetch_info(&token.token).await?;
|
let info = fetch_info(&token.token, semaphore).await?;
|
||||||
|
|
||||||
// Return structure from response
|
// Return structure from response
|
||||||
Ok(Credentials {
|
Ok(Credentials {
|
||||||
@@ -127,17 +133,16 @@ impl HydraAuthFlow<ws::tokio::ConnectStream> {
|
|||||||
|
|
||||||
pub async fn refresh_credentials(
|
pub async fn refresh_credentials(
|
||||||
credentials: &mut Credentials,
|
credentials: &mut Credentials,
|
||||||
|
semaphore: &RwLock<Semaphore>,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
let resp = crate::config::REQWEST_CLIENT
|
let resp = fetch_json::<TokenJSON>(
|
||||||
.post(HYDRA_URL.join("/refresh")?)
|
Method::POST,
|
||||||
.json(
|
HYDRA_URL.join("/refresh")?.as_str(),
|
||||||
&serde_json::json!({ "refresh_token": credentials.refresh_token }),
|
None,
|
||||||
)
|
Some(serde_json::json!({ "refresh_token": credentials.refresh_token })),
|
||||||
.send()
|
semaphore,
|
||||||
.await?
|
)
|
||||||
.error_for_status()?
|
.await?;
|
||||||
.json::<TokenJSON>()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
credentials.access_token = resp.token;
|
credentials.access_token = resp.token;
|
||||||
credentials.refresh_token = resp.refresh_token;
|
credentials.refresh_token = resp.refresh_token;
|
||||||
@@ -147,24 +152,21 @@ pub async fn refresh_credentials(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn refresh_username(
|
|
||||||
credentials: &mut Credentials,
|
|
||||||
) -> crate::Result<()> {
|
|
||||||
let info = fetch_info(&credentials.access_token).await?;
|
|
||||||
credentials.username = info.name;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
async fn fetch_info(token: &str) -> crate::Result<ProfileInfoJSON> {
|
async fn fetch_info(
|
||||||
let url =
|
token: &str,
|
||||||
Url::parse("https://api.minecraftservices.com/minecraft/profile")?;
|
semaphore: &RwLock<Semaphore>,
|
||||||
Ok(crate::config::REQWEST_CLIENT
|
) -> crate::Result<ProfileInfoJSON> {
|
||||||
.get(url)
|
let result = fetch_advanced(
|
||||||
.header(reqwest::header::AUTHORIZATION, format!("Bearer {token}"))
|
Method::GET,
|
||||||
.send()
|
"https://api.minecraftservices.com/minecraft/profile",
|
||||||
.await?
|
None,
|
||||||
.error_for_status()?
|
None,
|
||||||
.json::<ProfileInfoJSON>()
|
Some(("Authorization", &format!("Bearer {token}"))),
|
||||||
.await?)
|
semaphore,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let value = serde_json::from_slice(&result)?;
|
||||||
|
|
||||||
|
Ok(value)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,13 +100,14 @@ impl State {
|
|||||||
|
|
||||||
// On launcher initialization, attempt a tag fetch after tags init
|
// On launcher initialization, attempt a tag fetch after tags init
|
||||||
let mut tags = Tags::init(&database)?;
|
let mut tags = Tags::init(&database)?;
|
||||||
if let Err(tag_fetch_err) = tags.fetch_update().await {
|
if let Err(tag_fetch_err) =
|
||||||
|
tags.fetch_update(&io_semaphore).await
|
||||||
|
{
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
"Failed to fetch tags on launcher init: {}",
|
"Failed to fetch tags on launcher init: {}",
|
||||||
tag_fetch_err
|
tag_fetch_err
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// On launcher initialization, if global java variables are unset, try to find and set them
|
// On launcher initialization, if global java variables are unset, try to find and set them
|
||||||
// (they are required for the game to launch)
|
// (they are required for the game to launch)
|
||||||
if settings.java_globals.count() == 0 {
|
if settings.java_globals.count() == 0 {
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
use super::settings::{Hooks, MemorySettings, WindowSize};
|
use super::settings::{Hooks, MemorySettings, WindowSize};
|
||||||
|
use crate::config::MODRINTH_API_URL;
|
||||||
use crate::data::DirectoryInfo;
|
use crate::data::DirectoryInfo;
|
||||||
use crate::state::projects::Project;
|
use crate::state::projects::Project;
|
||||||
use crate::util::fetch::write_cached_icon;
|
use crate::state::{ModrinthVersion, ProjectType};
|
||||||
|
use crate::util::fetch::{fetch, fetch_json, write, write_cached_icon};
|
||||||
|
use crate::State;
|
||||||
use daedalus::modded::LoaderVersion;
|
use daedalus::modded::LoaderVersion;
|
||||||
use dunce::canonicalize;
|
use dunce::canonicalize;
|
||||||
use futures::prelude::*;
|
use futures::prelude::*;
|
||||||
|
use reqwest::Method;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::io::Cursor;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
@@ -129,12 +134,19 @@ impl Profile {
|
|||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
pub async fn sync(&mut self) -> crate::Result<()> {
|
||||||
pub fn set_java_settings(
|
let state = State::get().await?;
|
||||||
&mut self,
|
|
||||||
java: Option<JavaSettings>,
|
let paths = self.get_profile_project_paths()?;
|
||||||
) -> crate::Result<()> {
|
let projects = crate::state::infer_data_from_files(
|
||||||
self.java = java;
|
paths,
|
||||||
|
state.directories.caches_dir(),
|
||||||
|
&state.io_semaphore,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
self.projects = projects;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,13 +165,150 @@ impl Profile {
|
|||||||
Ok::<(), crate::Error>(())
|
Ok::<(), crate::Error>(())
|
||||||
};
|
};
|
||||||
|
|
||||||
read_paths("mods")?;
|
read_paths(ProjectType::Mod.get_folder())?;
|
||||||
read_paths("shaders")?;
|
read_paths(ProjectType::ShaderPack.get_folder())?;
|
||||||
read_paths("resourcepacks")?;
|
read_paths(ProjectType::ResourcePack.get_folder())?;
|
||||||
read_paths("datapacks")?;
|
read_paths(ProjectType::DataPack.get_folder())?;
|
||||||
|
|
||||||
Ok(files)
|
Ok(files)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn add_project_version(
|
||||||
|
&mut self,
|
||||||
|
version_id: String,
|
||||||
|
) -> crate::Result<PathBuf> {
|
||||||
|
let state = State::get().await?;
|
||||||
|
|
||||||
|
let version = fetch_json::<ModrinthVersion>(
|
||||||
|
Method::GET,
|
||||||
|
&format!("{MODRINTH_API_URL}version/{version_id}"),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
&state.io_semaphore,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let file = if let Some(file) = version.files.iter().find(|x| x.primary)
|
||||||
|
{
|
||||||
|
file
|
||||||
|
} else if let Some(file) = version.files.first() {
|
||||||
|
file
|
||||||
|
} else {
|
||||||
|
return Err(crate::ErrorKind::InputError(
|
||||||
|
"No files for input version present!".to_string(),
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
};
|
||||||
|
|
||||||
|
let bytes = fetch(
|
||||||
|
&file.url,
|
||||||
|
file.hashes.get("sha1").map(|x| &**x),
|
||||||
|
&state.io_semaphore,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let path = self
|
||||||
|
.add_project_bytes(
|
||||||
|
&file.filename,
|
||||||
|
bytes,
|
||||||
|
ProjectType::get_from_loaders(version.loaders),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add_project_bytes(
|
||||||
|
&mut self,
|
||||||
|
file_name: &str,
|
||||||
|
bytes: bytes::Bytes,
|
||||||
|
project_type: Option<ProjectType>,
|
||||||
|
) -> crate::Result<PathBuf> {
|
||||||
|
let project_type = if let Some(project_type) = project_type {
|
||||||
|
project_type
|
||||||
|
} else {
|
||||||
|
let cursor = Cursor::new(&*bytes);
|
||||||
|
|
||||||
|
let mut archive = zip::ZipArchive::new(cursor).map_err(|_| {
|
||||||
|
crate::ErrorKind::InputError(
|
||||||
|
"Unable to infer project type for input file".to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
if archive.by_name("fabric.mod.json").is_ok()
|
||||||
|
|| archive.by_name("quilt.mod.json").is_ok()
|
||||||
|
|| archive.by_name("META-INF/mods.toml").is_ok()
|
||||||
|
|| archive.by_name("mcmod.info").is_ok()
|
||||||
|
{
|
||||||
|
ProjectType::Mod
|
||||||
|
} else if archive.by_name("pack.mcmeta").is_ok() {
|
||||||
|
if archive.file_names().any(|x| x.starts_with("data/")) {
|
||||||
|
ProjectType::DataPack
|
||||||
|
} else {
|
||||||
|
ProjectType::ResourcePack
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(crate::ErrorKind::InputError(
|
||||||
|
"Unable to infer project type for input file".to_string(),
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let state = State::get().await?;
|
||||||
|
let path = self.path.join(project_type.get_folder()).join(file_name);
|
||||||
|
write(&path, &bytes, &state.io_semaphore).await?;
|
||||||
|
|
||||||
|
self.sync().await?;
|
||||||
|
|
||||||
|
Ok(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn toggle_disable_project(
|
||||||
|
&mut self,
|
||||||
|
path: &Path,
|
||||||
|
) -> crate::Result<()> {
|
||||||
|
if let Some(mut project) = self.projects.remove(path) {
|
||||||
|
let path = path.to_path_buf();
|
||||||
|
let mut new_path = path.clone();
|
||||||
|
|
||||||
|
if path.extension().map_or(false, |ext| ext == "disabled") {
|
||||||
|
project.disabled = false;
|
||||||
|
} else {
|
||||||
|
new_path.set_file_name(format!(
|
||||||
|
"{}.disabled",
|
||||||
|
path.file_name().unwrap_or_default().to_string_lossy()
|
||||||
|
));
|
||||||
|
project.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::rename(path, &new_path).await?;
|
||||||
|
|
||||||
|
self.projects.insert(new_path, project);
|
||||||
|
} else {
|
||||||
|
return Err(crate::ErrorKind::InputError(format!(
|
||||||
|
"Project path does not exist: {:?}",
|
||||||
|
path
|
||||||
|
))
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn remove_project(&mut self, path: &Path) -> crate::Result<()> {
|
||||||
|
if self.projects.contains_key(path) {
|
||||||
|
fs::remove_file(path).await?;
|
||||||
|
self.projects.remove(path);
|
||||||
|
} else {
|
||||||
|
return Err(crate::ErrorKind::InputError(format!(
|
||||||
|
"Project path does not exist: {:?}",
|
||||||
|
path
|
||||||
|
))
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Profiles {
|
impl Profiles {
|
||||||
@@ -258,7 +407,7 @@ impl Profiles {
|
|||||||
stream::iter(self.0.iter())
|
stream::iter(self.0.iter())
|
||||||
.map(Ok::<_, crate::Error>)
|
.map(Ok::<_, crate::Error>)
|
||||||
.try_for_each_concurrent(None, |(path, profile)| async move {
|
.try_for_each_concurrent(None, |(path, profile)| async move {
|
||||||
let json = serde_json::to_vec_pretty(&profile)?;
|
let json = serde_json::to_vec(&profile)?;
|
||||||
|
|
||||||
let json_path = Path::new(&path.to_string_lossy().to_string())
|
let json_path = Path::new(&path.to_string_lossy().to_string())
|
||||||
.join(PROFILE_JSON_PATH);
|
.join(PROFILE_JSON_PATH);
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
//! Project management + inference
|
//! Project management + inference
|
||||||
|
|
||||||
use crate::config::{MODRINTH_API_URL, REQWEST_CLIENT};
|
use crate::config::MODRINTH_API_URL;
|
||||||
use crate::util::fetch::write_cached_icon;
|
use crate::util::fetch::{fetch_json, write_cached_icon};
|
||||||
use async_zip::tokio::read::fs::ZipFileReader;
|
use async_zip::tokio::read::fs::ZipFileReader;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
use reqwest::Method;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use sha2::Digest;
|
use sha2::Digest;
|
||||||
@@ -12,13 +13,52 @@ use std::path::{Path, PathBuf};
|
|||||||
use tokio::io::AsyncReadExt;
|
use tokio::io::AsyncReadExt;
|
||||||
use tokio::sync::{RwLock, Semaphore};
|
use tokio::sync::{RwLock, Semaphore};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum ProjectType {
|
||||||
|
Mod,
|
||||||
|
DataPack,
|
||||||
|
ResourcePack,
|
||||||
|
ShaderPack,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProjectType {
|
||||||
|
pub fn get_from_loaders(loaders: Vec<String>) -> Option<Self> {
|
||||||
|
if loaders
|
||||||
|
.iter()
|
||||||
|
.any(|x| ["fabric", "forge", "quilt"].contains(&&**x))
|
||||||
|
{
|
||||||
|
Some(ProjectType::Mod)
|
||||||
|
} else if loaders.iter().any(|x| x == "datapack") {
|
||||||
|
Some(ProjectType::DataPack)
|
||||||
|
} else if loaders.iter().any(|x| ["iris", "optifine"].contains(&&**x)) {
|
||||||
|
Some(ProjectType::ShaderPack)
|
||||||
|
} else if loaders
|
||||||
|
.iter()
|
||||||
|
.any(|x| ["vanilla", "canvas", "minecraft"].contains(&&**x))
|
||||||
|
{
|
||||||
|
Some(ProjectType::ResourcePack)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_folder(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
ProjectType::Mod => "mods",
|
||||||
|
ProjectType::DataPack => "datapacks",
|
||||||
|
ProjectType::ResourcePack => "resourcepacks",
|
||||||
|
ProjectType::ShaderPack => "shaderpacks",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Project {
|
pub struct Project {
|
||||||
pub sha512: String,
|
pub sha512: String,
|
||||||
pub disabled: bool,
|
pub disabled: bool,
|
||||||
pub metadata: ProjectMetadata,
|
pub metadata: ProjectMetadata,
|
||||||
pub file_name: String,
|
pub file_name: String,
|
||||||
pub update_available: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
@@ -223,19 +263,20 @@ pub async fn infer_data_from_files(
|
|||||||
file_path_hashes.insert(hash, path.clone());
|
file_path_hashes.insert(hash, path.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let files: HashMap<String, ModrinthVersion> = REQWEST_CLIENT
|
let files: HashMap<String, ModrinthVersion> = fetch_json(
|
||||||
.post(format!("{}version_files", MODRINTH_API_URL))
|
Method::POST,
|
||||||
.json(&json!({
|
&format!("{}version_files", MODRINTH_API_URL),
|
||||||
|
None,
|
||||||
|
Some(json!({
|
||||||
"hashes": file_path_hashes.keys().collect::<Vec<_>>(),
|
"hashes": file_path_hashes.keys().collect::<Vec<_>>(),
|
||||||
"algorithm": "sha512",
|
"algorithm": "sha512",
|
||||||
}))
|
})),
|
||||||
.send()
|
io_semaphore,
|
||||||
.await?
|
)
|
||||||
.json()
|
.await?;
|
||||||
.await?;
|
let projects: Vec<ModrinthProject> = fetch_json(
|
||||||
|
Method::GET,
|
||||||
let projects: Vec<ModrinthProject> = REQWEST_CLIENT
|
&format!(
|
||||||
.get(format!(
|
|
||||||
"{}projects?ids={}",
|
"{}projects?ids={}",
|
||||||
MODRINTH_API_URL,
|
MODRINTH_API_URL,
|
||||||
serde_json::to_string(
|
serde_json::to_string(
|
||||||
@@ -244,27 +285,32 @@ pub async fn infer_data_from_files(
|
|||||||
.map(|x| x.project_id.clone())
|
.map(|x| x.project_id.clone())
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
)?
|
)?
|
||||||
))
|
),
|
||||||
.send()
|
None,
|
||||||
.await?
|
None,
|
||||||
.json()
|
io_semaphore,
|
||||||
.await?;
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let teams: Vec<ModrinthTeamMember> = REQWEST_CLIENT
|
let teams: Vec<ModrinthTeamMember> = fetch_json::<
|
||||||
.get(format!(
|
Vec<Vec<ModrinthTeamMember>>,
|
||||||
|
>(
|
||||||
|
Method::GET,
|
||||||
|
&format!(
|
||||||
"{}teams?ids={}",
|
"{}teams?ids={}",
|
||||||
MODRINTH_API_URL,
|
MODRINTH_API_URL,
|
||||||
serde_json::to_string(
|
serde_json::to_string(
|
||||||
&projects.iter().map(|x| x.team.clone()).collect::<Vec<_>>()
|
&projects.iter().map(|x| x.team.clone()).collect::<Vec<_>>()
|
||||||
)?
|
)?
|
||||||
))
|
),
|
||||||
.send()
|
None,
|
||||||
.await?
|
None,
|
||||||
.json::<Vec<Vec<ModrinthTeamMember>>>()
|
io_semaphore,
|
||||||
.await?
|
)
|
||||||
.into_iter()
|
.await?
|
||||||
.flatten()
|
.into_iter()
|
||||||
.collect();
|
.flatten()
|
||||||
|
.collect();
|
||||||
|
|
||||||
let mut return_projects = HashMap::new();
|
let mut return_projects = HashMap::new();
|
||||||
let mut further_analyze_projects: Vec<(String, PathBuf)> = Vec::new();
|
let mut further_analyze_projects: Vec<(String, PathBuf)> = Vec::new();
|
||||||
@@ -297,7 +343,6 @@ pub async fn infer_data_from_files(
|
|||||||
members: team_members,
|
members: team_members,
|
||||||
},
|
},
|
||||||
file_name,
|
file_name,
|
||||||
update_available: false,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
@@ -326,7 +371,6 @@ pub async fn infer_data_from_files(
|
|||||||
disabled: path.ends_with(".disabled"),
|
disabled: path.ends_with(".disabled"),
|
||||||
metadata: ProjectMetadata::Unknown,
|
metadata: ProjectMetadata::Unknown,
|
||||||
file_name,
|
file_name,
|
||||||
update_available: false,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
@@ -380,7 +424,6 @@ pub async fn infer_data_from_files(
|
|||||||
sha512: hash,
|
sha512: hash,
|
||||||
disabled: path.ends_with(".disabled"),
|
disabled: path.ends_with(".disabled"),
|
||||||
file_name,
|
file_name,
|
||||||
update_available: false,
|
|
||||||
metadata: ProjectMetadata::Inferred {
|
metadata: ProjectMetadata::Inferred {
|
||||||
title: Some(
|
title: Some(
|
||||||
pack.display_name
|
pack.display_name
|
||||||
@@ -447,7 +490,6 @@ pub async fn infer_data_from_files(
|
|||||||
sha512: hash,
|
sha512: hash,
|
||||||
disabled: path.ends_with(".disabled"),
|
disabled: path.ends_with(".disabled"),
|
||||||
file_name,
|
file_name,
|
||||||
update_available: false,
|
|
||||||
metadata: ProjectMetadata::Inferred {
|
metadata: ProjectMetadata::Inferred {
|
||||||
title: Some(if pack.name.is_empty() {
|
title: Some(if pack.name.is_empty() {
|
||||||
pack.modid
|
pack.modid
|
||||||
@@ -513,7 +555,6 @@ pub async fn infer_data_from_files(
|
|||||||
sha512: hash,
|
sha512: hash,
|
||||||
disabled: path.ends_with(".disabled"),
|
disabled: path.ends_with(".disabled"),
|
||||||
file_name,
|
file_name,
|
||||||
update_available: false,
|
|
||||||
metadata: ProjectMetadata::Inferred {
|
metadata: ProjectMetadata::Inferred {
|
||||||
title: Some(pack.name.unwrap_or(pack.id)),
|
title: Some(pack.name.unwrap_or(pack.id)),
|
||||||
description: pack.description,
|
description: pack.description,
|
||||||
@@ -579,7 +620,6 @@ pub async fn infer_data_from_files(
|
|||||||
sha512: hash,
|
sha512: hash,
|
||||||
disabled: path.ends_with(".disabled"),
|
disabled: path.ends_with(".disabled"),
|
||||||
file_name,
|
file_name,
|
||||||
update_available: false,
|
|
||||||
metadata: ProjectMetadata::Inferred {
|
metadata: ProjectMetadata::Inferred {
|
||||||
title: Some(
|
title: Some(
|
||||||
pack.metadata
|
pack.metadata
|
||||||
@@ -615,7 +655,7 @@ pub async fn infer_data_from_files(
|
|||||||
.file()
|
.file()
|
||||||
.entries()
|
.entries()
|
||||||
.iter()
|
.iter()
|
||||||
.position(|f| f.entry().filename() == "pack.mcdata");
|
.position(|f| f.entry().filename() == "pack.mcmeta");
|
||||||
if let Some(index) = zip_index_option {
|
if let Some(index) = zip_index_option {
|
||||||
let file = zip_file_reader.file().entries().get(index).unwrap();
|
let file = zip_file_reader.file().entries().get(index).unwrap();
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@@ -645,7 +685,6 @@ pub async fn infer_data_from_files(
|
|||||||
sha512: hash,
|
sha512: hash,
|
||||||
disabled: path.ends_with(".disabled"),
|
disabled: path.ends_with(".disabled"),
|
||||||
file_name,
|
file_name,
|
||||||
update_available: false,
|
|
||||||
metadata: ProjectMetadata::Inferred {
|
metadata: ProjectMetadata::Inferred {
|
||||||
title: None,
|
title: None,
|
||||||
description: pack.description,
|
description: pack.description,
|
||||||
@@ -666,7 +705,6 @@ pub async fn infer_data_from_files(
|
|||||||
sha512: hash,
|
sha512: hash,
|
||||||
disabled: path.ends_with(".disabled"),
|
disabled: path.ends_with(".disabled"),
|
||||||
file_name,
|
file_name,
|
||||||
update_available: false,
|
|
||||||
metadata: ProjectMetadata::Unknown,
|
metadata: ProjectMetadata::Unknown,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ impl Settings {
|
|||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub async fn sync(&self, to: &Path) -> crate::Result<()> {
|
pub async fn sync(&self, to: &Path) -> crate::Result<()> {
|
||||||
fs::write(to, serde_json::to_vec_pretty(self)?)
|
fs::write(to, serde_json::to_vec(self)?)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
crate::ErrorKind::FSError(format!(
|
crate::ErrorKind::FSError(format!(
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use bincode::{Decode, Encode};
|
use bincode::{Decode, Encode};
|
||||||
|
use reqwest::Method;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::sync::{RwLock, Semaphore};
|
||||||
|
|
||||||
use crate::config::{BINCODE_CONFIG, MODRINTH_API_URL, REQWEST_CLIENT};
|
use crate::config::{BINCODE_CONFIG, MODRINTH_API_URL};
|
||||||
|
use crate::util::fetch::fetch_json;
|
||||||
|
|
||||||
const CATEGORIES_DB_TREE: &[u8] = b"categories";
|
const CATEGORIES_DB_TREE: &[u8] = b"categories";
|
||||||
const LOADERS_DB_TREE: &[u8] = b"loaders";
|
const LOADERS_DB_TREE: &[u8] = b"loaders";
|
||||||
@@ -133,13 +136,17 @@ impl Tags {
|
|||||||
|
|
||||||
// Fetches the tags from the Modrinth API and stores them in the database
|
// Fetches the tags from the Modrinth API and stores them in the database
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub async fn fetch_update(&mut self) -> crate::Result<()> {
|
pub async fn fetch_update(
|
||||||
let categories = self.fetch_tag("category");
|
&mut self,
|
||||||
let loaders = self.fetch_tag("loader");
|
semaphore: &RwLock<Semaphore>,
|
||||||
let game_versions = self.fetch_tag("game_version");
|
) -> crate::Result<()> {
|
||||||
let licenses = self.fetch_tag("license");
|
let categories = format!("{MODRINTH_API_URL}tag/category");
|
||||||
let donation_platforms = self.fetch_tag("donation_platform");
|
let loaders = format!("{MODRINTH_API_URL}tag/loader");
|
||||||
let report_types = self.fetch_tag("report_type");
|
let game_versions = format!("{MODRINTH_API_URL}tag/game_version");
|
||||||
|
let licenses = format!("{MODRINTH_API_URL}tag/license");
|
||||||
|
let donation_platforms =
|
||||||
|
format!("{MODRINTH_API_URL}tag/donation_platform");
|
||||||
|
let report_types = format!("{MODRINTH_API_URL}tag/report_type");
|
||||||
let (
|
let (
|
||||||
categories,
|
categories,
|
||||||
loaders,
|
loaders,
|
||||||
@@ -148,70 +155,78 @@ impl Tags {
|
|||||||
donation_platforms,
|
donation_platforms,
|
||||||
report_types,
|
report_types,
|
||||||
) = tokio::try_join!(
|
) = tokio::try_join!(
|
||||||
categories,
|
fetch_json::<Vec<Category>>(
|
||||||
loaders,
|
Method::GET,
|
||||||
game_versions,
|
&categories,
|
||||||
licenses,
|
None,
|
||||||
donation_platforms,
|
None,
|
||||||
report_types
|
semaphore
|
||||||
|
),
|
||||||
|
fetch_json::<Vec<Loader>>(
|
||||||
|
Method::GET,
|
||||||
|
&loaders,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
semaphore
|
||||||
|
),
|
||||||
|
fetch_json::<Vec<GameVersion>>(
|
||||||
|
Method::GET,
|
||||||
|
&game_versions,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
semaphore
|
||||||
|
),
|
||||||
|
fetch_json::<Vec<License>>(
|
||||||
|
Method::GET,
|
||||||
|
&licenses,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
semaphore
|
||||||
|
),
|
||||||
|
fetch_json::<Vec<DonationPlatform>>(
|
||||||
|
Method::GET,
|
||||||
|
&donation_platforms,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
semaphore
|
||||||
|
),
|
||||||
|
fetch_json::<Vec<String>>(
|
||||||
|
Method::GET,
|
||||||
|
&report_types,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
semaphore
|
||||||
|
),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Store the tags in the database
|
// Store the tags in the database
|
||||||
self.0.categories.insert(
|
self.0.categories.insert(
|
||||||
"categories",
|
"categories",
|
||||||
bincode::encode_to_vec(
|
bincode::encode_to_vec(categories, *BINCODE_CONFIG)?,
|
||||||
categories.json::<Vec<Category>>().await?,
|
|
||||||
*BINCODE_CONFIG,
|
|
||||||
)?,
|
|
||||||
)?;
|
)?;
|
||||||
self.0.loaders.insert(
|
self.0.loaders.insert(
|
||||||
"loaders",
|
"loaders",
|
||||||
bincode::encode_to_vec(
|
bincode::encode_to_vec(loaders, *BINCODE_CONFIG)?,
|
||||||
loaders.json::<Vec<Loader>>().await?,
|
|
||||||
*BINCODE_CONFIG,
|
|
||||||
)?,
|
|
||||||
)?;
|
)?;
|
||||||
self.0.game_versions.insert(
|
self.0.game_versions.insert(
|
||||||
"game_versions",
|
"game_versions",
|
||||||
bincode::encode_to_vec(
|
bincode::encode_to_vec(game_versions, *BINCODE_CONFIG)?,
|
||||||
game_versions.json::<Vec<GameVersion>>().await?,
|
|
||||||
*BINCODE_CONFIG,
|
|
||||||
)?,
|
|
||||||
)?;
|
)?;
|
||||||
self.0.licenses.insert(
|
self.0.licenses.insert(
|
||||||
"licenses",
|
"licenses",
|
||||||
bincode::encode_to_vec(
|
bincode::encode_to_vec(licenses, *BINCODE_CONFIG)?,
|
||||||
licenses.json::<Vec<License>>().await?,
|
|
||||||
*BINCODE_CONFIG,
|
|
||||||
)?,
|
|
||||||
)?;
|
)?;
|
||||||
self.0.donation_platforms.insert(
|
self.0.donation_platforms.insert(
|
||||||
"donation_platforms",
|
"donation_platforms",
|
||||||
bincode::encode_to_vec(
|
bincode::encode_to_vec(donation_platforms, *BINCODE_CONFIG)?,
|
||||||
donation_platforms.json::<Vec<DonationPlatform>>().await?,
|
|
||||||
*BINCODE_CONFIG,
|
|
||||||
)?,
|
|
||||||
)?;
|
)?;
|
||||||
self.0.report_types.insert(
|
self.0.report_types.insert(
|
||||||
"report_types",
|
"report_types",
|
||||||
bincode::encode_to_vec(
|
bincode::encode_to_vec(report_types, *BINCODE_CONFIG)?,
|
||||||
report_types.json::<Vec<String>>().await?,
|
|
||||||
*BINCODE_CONFIG,
|
|
||||||
)?,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
|
||||||
pub async fn fetch_tag(
|
|
||||||
&self,
|
|
||||||
tag_type: &str,
|
|
||||||
) -> Result<reqwest::Response, reqwest::Error> {
|
|
||||||
let url = &format!("{MODRINTH_API_URL}tag/{}", tag_type);
|
|
||||||
let content = REQWEST_CLIENT.get(url).send().await?;
|
|
||||||
Ok(content)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serializeable struct for all tags to be fetched together by the frontend
|
// Serializeable struct for all tags to be fetched together by the frontend
|
||||||
|
|||||||
@@ -1,53 +1,86 @@
|
|||||||
//! Functions for fetching infromation from the Internet
|
//! Functions for fetching infromation from the Internet
|
||||||
use crate::config::REQWEST_CLIENT;
|
|
||||||
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::time;
|
||||||
use tokio::sync::{RwLock, Semaphore};
|
use tokio::sync::{RwLock, Semaphore};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
fs::{self, File},
|
fs::{self, File},
|
||||||
io::AsyncWriteExt,
|
io::AsyncWriteExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref REQWEST_CLIENT: reqwest::Client = {
|
||||||
|
let mut headers = reqwest::header::HeaderMap::new();
|
||||||
|
let header = reqwest::header::HeaderValue::from_str(&format!(
|
||||||
|
"modrinth/theseus/{} (support@modrinth.com)",
|
||||||
|
env!("CARGO_PKG_VERSION")
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
headers.insert(reqwest::header::USER_AGENT, header);
|
||||||
|
reqwest::Client::builder()
|
||||||
|
.tcp_keepalive(Some(time::Duration::from_secs(10)))
|
||||||
|
.default_headers(headers)
|
||||||
|
.build()
|
||||||
|
.expect("Reqwest Client Building Failed")
|
||||||
|
};
|
||||||
|
}
|
||||||
const FETCH_ATTEMPTS: usize = 3;
|
const FETCH_ATTEMPTS: usize = 3;
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(semaphore))]
|
||||||
pub async fn fetch(
|
pub async fn fetch(
|
||||||
url: &str,
|
url: &str,
|
||||||
sha1: Option<&str>,
|
sha1: Option<&str>,
|
||||||
semaphore: &RwLock<Semaphore>,
|
semaphore: &RwLock<Semaphore>,
|
||||||
) -> crate::Result<Bytes> {
|
) -> crate::Result<Bytes> {
|
||||||
fetch_advanced(Method::GET, url, sha1, semaphore).await
|
fetch_advanced(Method::GET, url, sha1, None, None, semaphore).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(json_body, semaphore))]
|
||||||
pub async fn fetch_json<T>(
|
pub async fn fetch_json<T>(
|
||||||
method: Method,
|
method: Method,
|
||||||
url: &str,
|
url: &str,
|
||||||
sha1: Option<&str>,
|
sha1: Option<&str>,
|
||||||
|
json_body: Option<serde_json::Value>,
|
||||||
semaphore: &RwLock<Semaphore>,
|
semaphore: &RwLock<Semaphore>,
|
||||||
) -> crate::Result<T>
|
) -> crate::Result<T>
|
||||||
where
|
where
|
||||||
T: DeserializeOwned,
|
T: DeserializeOwned,
|
||||||
{
|
{
|
||||||
let result = fetch_advanced(method, url, sha1, semaphore).await?;
|
let result =
|
||||||
|
fetch_advanced(method, url, sha1, json_body, None, semaphore).await?;
|
||||||
let value = serde_json::from_slice(&result)?;
|
let value = serde_json::from_slice(&result)?;
|
||||||
Ok(value)
|
Ok(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Downloads a file with retry and checksum functionality
|
/// Downloads a file with retry and checksum functionality
|
||||||
#[tracing::instrument(skip(semaphore))]
|
#[tracing::instrument(skip(json_body, semaphore))]
|
||||||
pub async fn fetch_advanced(
|
pub async fn fetch_advanced(
|
||||||
method: Method,
|
method: Method,
|
||||||
url: &str,
|
url: &str,
|
||||||
sha1: Option<&str>,
|
sha1: Option<&str>,
|
||||||
|
json_body: Option<serde_json::Value>,
|
||||||
|
header: Option<(&str, &str)>,
|
||||||
semaphore: &RwLock<Semaphore>,
|
semaphore: &RwLock<Semaphore>,
|
||||||
) -> crate::Result<Bytes> {
|
) -> crate::Result<Bytes> {
|
||||||
let io_semaphore = semaphore.read().await;
|
let io_semaphore = semaphore.read().await;
|
||||||
let _permit = io_semaphore.acquire().await?;
|
let _permit = io_semaphore.acquire().await?;
|
||||||
for attempt in 1..=(FETCH_ATTEMPTS + 1) {
|
|
||||||
let result = REQWEST_CLIENT.request(method.clone(), url).send().await;
|
|
||||||
|
|
||||||
|
for attempt in 1..=(FETCH_ATTEMPTS + 1) {
|
||||||
|
let mut req = REQWEST_CLIENT.request(method.clone(), url);
|
||||||
|
|
||||||
|
if let Some(body) = json_body.clone() {
|
||||||
|
req = req.json(&body);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(header) = header {
|
||||||
|
req = req.header(header.0, header.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = req.send().await;
|
||||||
match result {
|
match result {
|
||||||
Ok(x) => {
|
Ok(x) => {
|
||||||
let bytes = x.bytes().await;
|
let bytes = x.bytes().await;
|
||||||
|
|||||||
@@ -330,7 +330,7 @@ impl ProfileRun {
|
|||||||
))
|
))
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
let credentials = auth::refresh(id, false).await?;
|
let credentials = auth::refresh(id).await?;
|
||||||
|
|
||||||
let proc_lock = profile::run_credentials(&path, &credentials).await?;
|
let proc_lock = profile::run_credentials(&path, &credentials).await?;
|
||||||
let mut proc = proc_lock.write().await;
|
let mut proc = proc_lock.write().await;
|
||||||
|
|||||||
@@ -19,11 +19,8 @@ pub async fn auth_authenticate_await_completion() -> Result<Credentials> {
|
|||||||
/// Refresh some credentials using Hydra, if needed
|
/// Refresh some credentials using Hydra, if needed
|
||||||
// invoke('auth_refresh',user)
|
// invoke('auth_refresh',user)
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn auth_refresh(
|
pub async fn auth_refresh(user: uuid::Uuid) -> Result<Credentials> {
|
||||||
user: uuid::Uuid,
|
Ok(auth::refresh(user).await?)
|
||||||
update_name: bool,
|
|
||||||
) -> Result<Credentials> {
|
|
||||||
Ok(auth::refresh(user, update_name).await?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a user account from the database
|
/// Remove a user account from the database
|
||||||
|
|||||||
@@ -27,6 +27,51 @@ pub async fn profile_list(
|
|||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adds a project to a profile from a version ID
|
||||||
|
// invoke('profile_add_project_from_version')
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn profile_add_project_from_version(
|
||||||
|
path: &Path,
|
||||||
|
version_id: String,
|
||||||
|
) -> Result<PathBuf> {
|
||||||
|
let res = profile::add_project_from_version(path, version_id).await?;
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a project to a profile from a path
|
||||||
|
// invoke('profile_add_project_from_path')
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn profile_add_project_from_path(
|
||||||
|
path: &Path,
|
||||||
|
project_path: &Path,
|
||||||
|
project_type: Option<String>,
|
||||||
|
) -> Result<PathBuf> {
|
||||||
|
let res = profile::add_project_from_path(path, project_path, project_type)
|
||||||
|
.await?;
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggles disabling a project from its path
|
||||||
|
// invoke('profile_toggle_disable_project')
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn profile_toggle_disable_project(
|
||||||
|
path: &Path,
|
||||||
|
project_path: &Path,
|
||||||
|
) -> Result<()> {
|
||||||
|
profile::toggle_disable_project(path, project_path).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes a project from a profile
|
||||||
|
// invoke('profile_remove_project')
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn profile_remove_project(
|
||||||
|
path: &Path,
|
||||||
|
project_path: &Path,
|
||||||
|
) -> Result<()> {
|
||||||
|
profile::remove_project(path, project_path).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
// Run minecraft using a profile using the default credentials
|
// Run minecraft using a profile using the default credentials
|
||||||
// Returns a u32 representing the PID, which can be used to poll
|
// Returns a u32 representing the PID, which can be used to poll
|
||||||
// for the actual Child in the state.
|
// for the actual Child in the state.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::api::Result;
|
use crate::api::Result;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use theseus::prelude::*;
|
use theseus::prelude::*;
|
||||||
|
|
||||||
// Identical to theseus::settings::Settings except for the custom_java_args field
|
// Identical to theseus::settings::Settings except for the custom_java_args field
|
||||||
@@ -43,7 +43,11 @@ pub async fn settings_set(settings: FrontendSettings) -> Result<()> {
|
|||||||
let backend_settings = Settings {
|
let backend_settings = Settings {
|
||||||
memory: settings.memory,
|
memory: settings.memory,
|
||||||
game_resolution: settings.game_resolution,
|
game_resolution: settings.game_resolution,
|
||||||
custom_java_args: settings.custom_java_args.split_whitespace().map(|s| s.to_string()).collect(),
|
custom_java_args: settings
|
||||||
|
.custom_java_args
|
||||||
|
.split_whitespace()
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect(),
|
||||||
custom_env_args: settings.custom_env_args,
|
custom_env_args: settings.custom_env_args,
|
||||||
java_globals: settings.java_globals,
|
java_globals: settings.java_globals,
|
||||||
default_user: settings.default_user,
|
default_user: settings.default_user,
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ fn main() {
|
|||||||
api::profile::profile_remove,
|
api::profile::profile_remove,
|
||||||
api::profile::profile_get,
|
api::profile::profile_get,
|
||||||
api::profile::profile_list,
|
api::profile::profile_list,
|
||||||
|
api::profile::profile_add_project_from_version,
|
||||||
|
api::profile::profile_add_project_from_path,
|
||||||
|
api::profile::profile_toggle_disable_project,
|
||||||
|
api::profile::profile_remove_project,
|
||||||
api::profile::profile_run,
|
api::profile::profile_run,
|
||||||
api::profile::profile_run_wait,
|
api::profile::profile_run_wait,
|
||||||
api::profile::profile_run_credentials,
|
api::profile::profile_run_credentials,
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import { RouterView, RouterLink } from 'vue-router'
|
|||||||
import {
|
import {
|
||||||
ChevronLeftIcon,
|
ChevronLeftIcon,
|
||||||
ChevronRightIcon,
|
ChevronRightIcon,
|
||||||
|
HomeIcon,
|
||||||
SearchIcon,
|
SearchIcon,
|
||||||
BookIcon,
|
LibraryIcon,
|
||||||
ClientIcon,
|
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
} from 'omorphia'
|
} from 'omorphia'
|
||||||
@@ -42,9 +42,9 @@ list().then(
|
|||||||
<AccountsCard ref="accounts" />
|
<AccountsCard ref="accounts" />
|
||||||
</suspense>
|
</suspense>
|
||||||
<div class="pages-list">
|
<div class="pages-list">
|
||||||
<RouterLink to="/" class="button-base nav-button"><ClientIcon /></RouterLink>
|
<RouterLink to="/" class="button-base nav-button"><HomeIcon /></RouterLink>
|
||||||
<RouterLink to="/browse" class="button-base nav-button"> <SearchIcon /></RouterLink>
|
<RouterLink to="/browse" class="button-base nav-button"> <SearchIcon /></RouterLink>
|
||||||
<RouterLink to="/library" class="button-base nav-button"> <BookIcon /></RouterLink>
|
<RouterLink to="/library" class="button-base nav-button"> <LibraryIcon /></RouterLink>
|
||||||
<button color="primary" class="button-base primary nav-button" icon-only>
|
<button color="primary" class="button-base primary nav-button" icon-only>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ export async function addDefaultInstance() {
|
|||||||
return await invoke('profile_create_empty')
|
return await invoke('profile_create_empty')
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add empty default instance
|
/// Creates instance
|
||||||
/// Returns a path to the profile created
|
/// Returns a path to the profile created
|
||||||
export async function create() {
|
export async function create(name, game_version, modloader, loader_version, icon) {
|
||||||
return await invoke('profile_create')
|
return await invoke('profile_create', { name, game_version, modloader, loader_version, icon })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove a profile
|
// Remove a profile
|
||||||
@@ -33,6 +33,28 @@ export async function list() {
|
|||||||
return await invoke('profile_list')
|
return await invoke('profile_list')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add a project to a profile from a version
|
||||||
|
// Returns a path to the new project file
|
||||||
|
export async function add_project_from_version(path, version_id) {
|
||||||
|
return await invoke('profile_add_project_from_version', { path, version_id })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a project to a profile from a path + project_type
|
||||||
|
// Returns a path to the new project file
|
||||||
|
export async function add_project_from_path(path, project_path, project_type) {
|
||||||
|
return await invoke('profile_add_project_from_path', { path, project_path, project_type })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle disabling a project
|
||||||
|
export async function toggle_disable_project(path, project_path) {
|
||||||
|
return await invoke('profile_toggle_disable_project', { path, project_path })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove a project
|
||||||
|
export async function remove_project(path, project_path) {
|
||||||
|
return await invoke('profile_remove_project', { path, project_path })
|
||||||
|
}
|
||||||
|
|
||||||
// Run Minecraft using a pathed profile
|
// Run Minecraft using a pathed profile
|
||||||
// Returns PID of child
|
// Returns PID of child
|
||||||
export async function run(path) {
|
export async function run(path) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
use dunce::canonicalize;
|
use dunce::canonicalize;
|
||||||
use theseus::prelude::*;
|
use theseus::prelude::*;
|
||||||
|
use theseus::profile_create::profile_create;
|
||||||
use tokio::time::{sleep, Duration};
|
use tokio::time::{sleep, Duration};
|
||||||
|
|
||||||
// A simple Rust implementation of the authentication run
|
// A simple Rust implementation of the authentication run
|
||||||
@@ -46,14 +47,46 @@ async fn main() -> theseus::Result<()> {
|
|||||||
|
|
||||||
println!("Creating/adding profile.");
|
println!("Creating/adding profile.");
|
||||||
|
|
||||||
let profile_path =
|
let name = "Example".to_string();
|
||||||
pack::install_pack_from_version_id("KxUUUFh5".to_string())
|
let game_version = "1.19.2".to_string();
|
||||||
.await
|
let modloader = ModLoader::Fabric;
|
||||||
.unwrap();
|
let loader_version = "stable".to_string();
|
||||||
|
|
||||||
|
let profile_path = profile_create(
|
||||||
|
name.clone(),
|
||||||
|
game_version,
|
||||||
|
modloader,
|
||||||
|
Some(loader_version),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("Adding sodium");
|
||||||
|
let sodium_path = profile::add_project_from_version(
|
||||||
|
&profile_path,
|
||||||
|
"rAfhHfow".to_string(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mod_menu_path = profile::add_project_from_version(
|
||||||
|
&profile_path,
|
||||||
|
"gSoPJyVn".to_string(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("Disabling sodium");
|
||||||
|
profile::toggle_disable_project(&profile_path, &sodium_path).await?;
|
||||||
|
|
||||||
|
profile::remove_project(&profile_path, &mod_menu_path).await?;
|
||||||
|
// let profile_path =
|
||||||
|
// pack::install_pack_from_version_id("KxUUUFh5".to_string())
|
||||||
|
// .await
|
||||||
|
// .unwrap();
|
||||||
|
|
||||||
// async closure for testing any desired edits
|
// async closure for testing any desired edits
|
||||||
// (ie: changing the java runtime of an added profile)
|
// (ie: changing the java runtime of an added profile)
|
||||||
// println!("Editing.");
|
println!("Editing.");
|
||||||
profile::edit(&profile_path, |_profile| {
|
profile::edit(&profile_path, |_profile| {
|
||||||
// Eg: Java- this would let you change the java runtime of the profile instead of using the default
|
// Eg: Java- this would let you change the java runtime of the profile instead of using the default
|
||||||
// use theseus::prelude::jre::JAVA__KEY;
|
// use theseus::prelude::jre::JAVA__KEY;
|
||||||
@@ -72,6 +105,7 @@ async fn main() -> theseus::Result<()> {
|
|||||||
authenticate_run().await?; // could take credentials from here direct, but also deposited in state users
|
authenticate_run().await?; // could take credentials from here direct, but also deposited in state users
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println!("running");
|
||||||
// Run a profile, running minecraft and store the RwLock to the process
|
// Run a profile, running minecraft and store the RwLock to the process
|
||||||
let proc_lock = profile::run(&canonicalize(&profile_path)?).await?;
|
let proc_lock = profile::run(&canonicalize(&profile_path)?).await?;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user