feat: process management + os specific settings + proxied events + launch half done
This commit is contained in:
parent
a8eb841bd7
commit
71d584e5a3
44 changed files with 1001 additions and 315 deletions
24
Cargo.lock
generated
24
Cargo.lock
generated
|
|
@ -2542,6 +2542,28 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "merge"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e520ba58faea3487f75df198b1d079644ec226ea3b0507d002c6fa4b8cf93a"
|
||||
dependencies = [
|
||||
"merge_derive",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "merge_derive"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c8f8ce6efff81cbc83caf4af0905c46e58cb46892f63ad3835e81b47eaf7968"
|
||||
dependencies = [
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
|
|
@ -3020,6 +3042,7 @@ dependencies = [
|
|||
"indicatif",
|
||||
"interpulse",
|
||||
"md-5",
|
||||
"merge",
|
||||
"murmur2",
|
||||
"onelauncher_entity",
|
||||
"onelauncher_gamemode",
|
||||
|
|
@ -3063,6 +3086,7 @@ version = "0.2.0-alpha.2"
|
|||
dependencies = [
|
||||
"chrono",
|
||||
"interpulse",
|
||||
"merge",
|
||||
"onelauncher_macro",
|
||||
"sea-orm",
|
||||
"serde",
|
||||
|
|
|
|||
|
|
@ -149,6 +149,7 @@ async-trait = { version = "0.1" }
|
|||
whoami = { version = "1.5" }
|
||||
cc = { version = "1.1" }
|
||||
libc = { version = "0.2" }
|
||||
merge = { version = "0.2" }
|
||||
|
||||
# macro
|
||||
syn = { version = "2" }
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ url = { workspace = true }
|
|||
async-trait = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
byteorder = { workspace = true }
|
||||
merge = { workspace = true }
|
||||
|
||||
# integration
|
||||
discord-rich-presence = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ async fn main() -> LauncherResult<()> {
|
|||
}
|
||||
|
||||
println!("creating cluster");
|
||||
let cluster = api::cluster::create_cluster(
|
||||
let cluster = &mut api::cluster::create_cluster(
|
||||
"Test Cluster",
|
||||
"1.8.9",
|
||||
GameLoader::Vanilla,
|
||||
|
|
@ -23,7 +23,7 @@ async fn main() -> LauncherResult<()> {
|
|||
println!("cluster created: {cluster:#?}");
|
||||
|
||||
println!("preparing cluster for launch");
|
||||
api::cluster::prepare_cluster(&cluster).await?;
|
||||
api::cluster::prepare_cluster(cluster).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ pub async fn insert_cluster(
|
|||
|
||||
/// Updates an existing cluster in the database.
|
||||
pub async fn update_cluster_by_id<B>(
|
||||
id: i32,
|
||||
id: u64,
|
||||
block: B,
|
||||
) -> LauncherResult<clusters::Model>
|
||||
where B: AsyncFnOnce(clusters::ActiveModel) -> LauncherResult<clusters::ActiveModel> {
|
||||
|
|
@ -48,8 +48,25 @@ where B: AsyncFnOnce(clusters::ActiveModel) -> LauncherResult<clusters::ActiveMo
|
|||
Ok(model)
|
||||
}
|
||||
|
||||
pub async fn update_cluster<B>(
|
||||
cluster: &mut clusters::Model,
|
||||
block: B,
|
||||
) -> LauncherResult<&mut clusters::Model>
|
||||
where B: AsyncFnOnce(clusters::ActiveModel) -> LauncherResult<clusters::ActiveModel> {
|
||||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
|
||||
let model = cluster.clone().into_active_model();
|
||||
let model = block(model).await?;
|
||||
let model = model.update(db).await?;
|
||||
|
||||
*cluster = model;
|
||||
|
||||
Ok(cluster)
|
||||
}
|
||||
|
||||
/// Deletes a cluster by its ID from the **database**.
|
||||
pub async fn delete_cluster_by_id(id: i32) -> LauncherResult<()> {
|
||||
pub async fn delete_cluster_by_id(id: u64) -> LauncherResult<()> {
|
||||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
|
||||
|
|
@ -65,7 +82,7 @@ pub async fn delete_cluster_by_id(id: i32) -> LauncherResult<()> {
|
|||
}
|
||||
|
||||
/// Gets a cluster by its ID from the database.
|
||||
pub async fn get_cluster_by_id(id: i32) -> LauncherResult<Option<clusters::Model>> {
|
||||
pub async fn get_cluster_by_id(id: u64) -> LauncherResult<Option<clusters::Model>> {
|
||||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use chrono::Utc;
|
||||
use interpulse::api::minecraft::VersionInfo;
|
||||
use onelauncher_entity::cluster_stage::ClusterStage;
|
||||
use onelauncher_entity::{clusters, java_versions};
|
||||
|
|
@ -26,6 +27,10 @@ use super::ingress::init_ingress;
|
|||
pub enum ClusterError {
|
||||
#[error("version '{0}' was not found")]
|
||||
InvalidVersion(String),
|
||||
#[error("failed to imply java version")]
|
||||
MissingJavaVersion,
|
||||
#[error("cluster is in downloading stage")]
|
||||
ClusterDownloading,
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
|
|
@ -97,7 +102,7 @@ pub async fn create_cluster(
|
|||
}
|
||||
|
||||
/// Make a cluster playable (installs all necessary game files and dependencies required to play)
|
||||
pub async fn prepare_cluster(cluster: &clusters::Model) -> LauncherResult<()> {
|
||||
pub async fn prepare_cluster(cluster: &mut clusters::Model) -> LauncherResult<&clusters::Model> {
|
||||
const INGRESS_TOTAL: f64 = 100.0;
|
||||
const INGRESS_TASKS: f64 = 4.0;
|
||||
const INGRESS_STEP: f64 = INGRESS_TOTAL / INGRESS_TASKS;
|
||||
|
|
@ -106,7 +111,7 @@ pub async fn prepare_cluster(cluster: &clusters::Model) -> LauncherResult<()> {
|
|||
|
||||
if cluster.stage == ClusterStage::Ready {
|
||||
tracing::info!("cluster is already ready");
|
||||
return Ok(());
|
||||
return Ok(cluster);
|
||||
}
|
||||
|
||||
let ingress_id = init_ingress(
|
||||
|
|
@ -118,7 +123,7 @@ pub async fn prepare_cluster(cluster: &clusters::Model) -> LauncherResult<()> {
|
|||
)
|
||||
.await?;
|
||||
|
||||
dao::update_cluster_by_id(cluster.id, async |mut cluster| {
|
||||
dao::update_cluster(cluster, async |mut cluster| {
|
||||
cluster.stage = Set(ClusterStage::Downloading);
|
||||
Ok(cluster)
|
||||
})
|
||||
|
|
@ -128,7 +133,7 @@ pub async fn prepare_cluster(cluster: &clusters::Model) -> LauncherResult<()> {
|
|||
// TASK 1
|
||||
ingress_id.set_ingress_message("fetching data").await?;
|
||||
let setting_profile =
|
||||
setting_profiles::get_profile_or_default(cluster.setting_profile_name.as_ref()).await?;
|
||||
setting_profiles::dao::get_profile_or_default(cluster.setting_profile_name.as_ref()).await?;
|
||||
|
||||
let state = State::get().await?;
|
||||
let mut metadata = state.metadata.write().await;
|
||||
|
|
@ -160,22 +165,11 @@ pub async fn prepare_cluster(cluster: &clusters::Model) -> LauncherResult<()> {
|
|||
let java_version = if let Some(version) = java::get_recommended_java(&version_info, Some(&setting_profile)).await? {
|
||||
version
|
||||
} else {
|
||||
// TODO: java runtime not found - should prob download
|
||||
let java = java::locate_java().await?;
|
||||
let Some(ver) = &version_info.java_version else {
|
||||
return Err(ClusterError::MissingJavaVersion.into());
|
||||
};
|
||||
|
||||
if java.is_empty() {
|
||||
tracing::warn!("No Java installations found");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for (path, info) in java {
|
||||
java::dao::insert_java(path, info).await?;
|
||||
}
|
||||
|
||||
java::get_recommended_java(&version_info, Some(&setting_profile)).await?
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!("No Java installations found, please install a Java runtime")
|
||||
})?
|
||||
java::prepare_java(ver.major_version).await?
|
||||
};
|
||||
|
||||
// TASK 3
|
||||
|
|
@ -189,7 +183,7 @@ pub async fn prepare_cluster(cluster: &clusters::Model) -> LauncherResult<()> {
|
|||
|
||||
if let Err(err) = result {
|
||||
tracing::error!("failed to prepare cluster: {}", err);
|
||||
dao::update_cluster_by_id(cluster.id, async |mut cluster| {
|
||||
dao::update_cluster(cluster, async |mut cluster| {
|
||||
cluster.stage = Set(ClusterStage::NotReady);
|
||||
Ok(cluster)
|
||||
})
|
||||
|
|
@ -197,14 +191,14 @@ pub async fn prepare_cluster(cluster: &clusters::Model) -> LauncherResult<()> {
|
|||
return Err(err);
|
||||
}
|
||||
|
||||
dao::update_cluster_by_id(cluster.id, async |mut cluster| {
|
||||
dao::update_cluster(cluster, async |mut cluster| {
|
||||
cluster.stage = Set(ClusterStage::Ready);
|
||||
Ok(cluster)
|
||||
})
|
||||
.await?;
|
||||
tracing::debug!("cluster is ready");
|
||||
|
||||
Ok(())
|
||||
Ok(cluster)
|
||||
}
|
||||
|
||||
/// Run forge processors
|
||||
|
|
@ -308,3 +302,14 @@ async fn run_forge_processors(
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_playtime(id: u64, duration: i64) -> LauncherResult<clusters::Model> {
|
||||
dao::update_cluster_by_id(id, async |mut cluster| {
|
||||
let overall_played = cluster.overall_played.take().unwrap_or_default().unwrap_or_default();
|
||||
|
||||
cluster.overall_played = Set(Some(overall_played + duration));
|
||||
cluster.last_played = Set(Some(Utc::now().timestamp()));
|
||||
|
||||
Ok(cluster)
|
||||
}).await
|
||||
}
|
||||
|
|
@ -26,7 +26,7 @@ impl SyncAction {
|
|||
|
||||
/// Syncs all clusters in the database. Returns a list of cluster IDs that are missing.
|
||||
#[tracing::instrument]
|
||||
pub async fn sync_clusters() -> LauncherResult<Vec<i32>> {
|
||||
pub async fn sync_clusters() -> LauncherResult<Vec<u64>> {
|
||||
let clusters = dao::get_all_clusters().await?;
|
||||
|
||||
let mut missing_ids = Vec::new();
|
||||
|
|
@ -92,7 +92,7 @@ pub async fn sync_cluster(cluster: &clusters::Model) -> LauncherResult<SyncActio
|
|||
/// Syncs the cluster with the database.
|
||||
/// Checks if the cluster in the database exists and if the directory exists.
|
||||
#[tracing::instrument]
|
||||
pub async fn sync_cluster_by_id(id: i32) -> LauncherResult<SyncAction> {
|
||||
pub async fn sync_cluster_by_id(id: u64) -> LauncherResult<SyncAction> {
|
||||
let cluster = dao::get_cluster_by_id(id)
|
||||
.await?
|
||||
.ok_or(DaoError::NotFound)?;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use onelauncher_entity::{cluster_stage::ClusterStage, prelude::model::*};
|
||||
use tokio::sync::RwLock;
|
||||
use merge::Merge;
|
||||
|
||||
use crate::{api::{cluster::{prepare_cluster, ClusterError}, game::metadata, java::{self, JavaError}, setting_profiles::{dao::get_profile_by_name, get_global_profile}}, error::LauncherResult, store::{credentials::MinecraftCredentials, processes::Process, State}, utils::io};
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn launch_minecraft(
|
||||
cluster: &mut Cluster,
|
||||
creds: &MinecraftCredentials,
|
||||
) -> LauncherResult<Arc<RwLock<Process>>> {
|
||||
if cluster.stage.is_downloading() {
|
||||
return Err(ClusterError::ClusterDownloading.into());
|
||||
} else if cluster.stage == ClusterStage::NotReady {
|
||||
prepare_cluster(cluster).await?;
|
||||
}
|
||||
|
||||
let mut settings = get_global_profile().await;
|
||||
if let Some(name) = &cluster.setting_profile_name {
|
||||
if let Some(profile) = get_profile_by_name(name).await? {
|
||||
settings.merge(profile);
|
||||
}
|
||||
}
|
||||
|
||||
let state = State::get().await?;
|
||||
let instance_path = &io::canonicalize(&cluster.path)?;
|
||||
|
||||
let mut metadata = state.metadata.write().await;
|
||||
let versions = &metadata.get_vanilla_or_fetch().await?.versions;
|
||||
|
||||
let version_index = versions
|
||||
.iter()
|
||||
.position(|it| it.id == cluster.mc_version)
|
||||
.ok_or_else(|| anyhow::anyhow!("invalid game version {}", cluster.mc_version))?;
|
||||
|
||||
|
||||
let version = &versions[version_index];
|
||||
let updated = version_index <= versions.iter().position(|x| x.id == "22w16a").unwrap_or(0);
|
||||
|
||||
let version_jar_name = cluster
|
||||
.mc_loader_version
|
||||
.as_ref()
|
||||
.map_or(version.id.clone(), |it| {
|
||||
format!("{}-{}", version.id.clone(), it.clone())
|
||||
});
|
||||
|
||||
let loader_version = metadata::get_loader_version(&cluster.mc_version, cluster.mc_loader, cluster.mc_loader_version.as_deref()).await?;
|
||||
|
||||
let version_info = metadata::download_version_info(
|
||||
version,
|
||||
loader_version.as_ref(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
drop(metadata);
|
||||
|
||||
let java = java::get_recommended_java(&version_info, Some(&settings)).await?;
|
||||
if java.is_none() {
|
||||
return Err(JavaError::MissingJava.into());
|
||||
}
|
||||
|
||||
|
||||
|
||||
todo!()
|
||||
}
|
||||
27
packages/core/src/api/game/log.rs
Normal file
27
packages/core/src/api/game/log.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use std::sync::LazyLock;
|
||||
|
||||
use crate::store::credentials::MinecraftCredentials;
|
||||
|
||||
static WHOAMI: LazyLock<(String, String)> = LazyLock::new(|| {
|
||||
(whoami::username(), whoami::realname())
|
||||
});
|
||||
|
||||
#[must_use]
|
||||
pub fn censor_line(
|
||||
creds: &MinecraftCredentials,
|
||||
mut output: String
|
||||
) -> String {
|
||||
let username = &WHOAMI.0;
|
||||
let realname = &WHOAMI.1;
|
||||
|
||||
output = output.replace(&format!("/{username}/"), "/{ENV_USERNAME}/");
|
||||
output = output.replace(&format!("\\{username}\\"), "\\{ENV_USERNAME}\\");
|
||||
output = output.replace(&format!("/{realname}/"), "/{ENV_REALNAME}/");
|
||||
output = output.replace(&format!("\\{realname}\\"), "\\{ENV_REALNAME}\\");
|
||||
output = output.replace(&creds.access_token, "{MC_ACCESS_TOKEN}");
|
||||
output = output.replace(&creds.username, "{MC_USERNAME}");
|
||||
output = output.replace(&creds.id.as_simple().to_string(), "{MC_UUID}");
|
||||
output = output.replace(&creds.id.as_hyphenated().to_string(), "{MC_UUID}");
|
||||
|
||||
output
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
pub mod metadata;
|
||||
pub mod launch;
|
||||
pub mod arguments;
|
||||
pub mod rules;
|
||||
pub mod launch;
|
||||
pub mod log;
|
||||
pub mod metadata;
|
||||
pub mod rules;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,20 @@
|
|||
use serde::Serialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::LauncherResult;
|
||||
use crate::store::State;
|
||||
use crate::store::ingress::{IngressId, IngressType, SubIngress};
|
||||
|
||||
#[onelauncher_macro::specta(with_event)]
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct IngressPayload {
|
||||
pub id: Uuid,
|
||||
pub message: String,
|
||||
pub ingress_type: IngressType,
|
||||
pub percent: Option<f64>,
|
||||
pub total: f64,
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn init_ingress(
|
||||
ingress_type: IngressType,
|
||||
|
|
|
|||
|
|
@ -20,13 +20,13 @@ pub async fn get_latest_java() -> LauncherResult<Option<java_versions::Model>> {
|
|||
|
||||
/// Accepts a path to a JRE folder and a [`JavaInfo`] and converts it to a [`java_versions::ActiveModel`]
|
||||
pub fn get_java_model(absolute_path: &Path, info: &JavaInfo) -> LauncherResult<java_versions::ActiveModel> {
|
||||
let major_version = if info.java_version[0..2].eq("1.") {
|
||||
info.java_version[2..3].parse::<i32>()
|
||||
let major_version: u32 = if info.java_version[0..2].eq("1.") {
|
||||
info.java_version[2..3].parse()
|
||||
} else {
|
||||
let split = info.java_version.split_once('.').unwrap_or_default();
|
||||
split.0.parse::<i32>()
|
||||
split.0.parse()
|
||||
}.or_else(
|
||||
|_| info.java_version.parse::<i32>()
|
||||
|_| info.java_version.parse()
|
||||
).map_err(|e| JavaError::ParseVersion(info.java_version.clone(), e))?;
|
||||
|
||||
Ok(java_versions::ActiveModel {
|
||||
|
|
@ -50,7 +50,7 @@ pub async fn get_java_all() -> LauncherResult<Vec<java_versions::Model>> {
|
|||
}
|
||||
|
||||
/// Returns the specific Java version by ID
|
||||
pub async fn get_java_by_id(id: i32) -> LauncherResult<Option<java_versions::Model>> {
|
||||
pub async fn get_java_by_id(id: u64) -> LauncherResult<Option<java_versions::Model>> {
|
||||
let db = &State::get().await?.db;
|
||||
|
||||
Ok(java_versions::Entity::find()
|
||||
|
|
@ -60,7 +60,7 @@ pub async fn get_java_by_id(id: i32) -> LauncherResult<Option<java_versions::Mod
|
|||
}
|
||||
|
||||
/// Returns all versions of Java for a given major version
|
||||
pub async fn get_all_java_by_major(major: i32) -> LauncherResult<Vec<java_versions::Model>> {
|
||||
pub async fn get_all_java_by_major(major: u32) -> LauncherResult<Vec<java_versions::Model>> {
|
||||
let db = &State::get().await?.db;
|
||||
|
||||
Ok(java_versions::Entity::find()
|
||||
|
|
@ -71,7 +71,7 @@ pub async fn get_all_java_by_major(major: i32) -> LauncherResult<Vec<java_versio
|
|||
}
|
||||
|
||||
/// Returns the latest Java version for a given major version
|
||||
pub async fn get_latest_java_by_major(major: i32) -> LauncherResult<Option<java_versions::Model>> {
|
||||
pub async fn get_latest_java_by_major(major: u32) -> LauncherResult<Option<java_versions::Model>> {
|
||||
let db = &State::get().await?.db;
|
||||
|
||||
Ok(java_versions::Entity::find()
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ pub enum JavaError {
|
|||
ParseVersion(String, #[source] std::num::ParseIntError),
|
||||
#[error("failed to execute java command")]
|
||||
Execute(#[from] std::io::Error),
|
||||
#[error("no java installations found")]
|
||||
MissingJava,
|
||||
}
|
||||
|
||||
/// Returns the relative path to the Java executable
|
||||
|
|
@ -133,8 +135,43 @@ pub async fn get_recommended_java(
|
|||
return Ok(None);
|
||||
};
|
||||
|
||||
#[allow(clippy::cast_possible_wrap)]
|
||||
dao::get_latest_java_by_major(supported_ver.major_version as i32).await
|
||||
dao::get_latest_java_by_major(supported_ver.major_version).await
|
||||
}
|
||||
|
||||
pub async fn prepare_java(
|
||||
major: u32,
|
||||
) -> LauncherResult<java_versions::Model> {
|
||||
let id = init_ingress(
|
||||
IngressType::JavaPrepare,
|
||||
"preparing java",
|
||||
100.0,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let java = dao::get_latest_java_by_major(major).await?;
|
||||
if let Some(java) = &java {
|
||||
send_ingress(&id, 100.0).await?;
|
||||
return Ok(java.clone());
|
||||
}
|
||||
|
||||
let java = locate_java().await?;
|
||||
|
||||
if !java.is_empty() {
|
||||
for (path, info) in java {
|
||||
dao::insert_java(path, info).await?;
|
||||
}
|
||||
|
||||
if let Some(java) = dao::get_latest_java_by_major(major).await? {
|
||||
send_ingress(&id, 100.0).await?;
|
||||
return Ok(java);
|
||||
}
|
||||
}
|
||||
|
||||
tracing::warn!("no java installations found on the system, please install java manually or use the launcher to download it");
|
||||
|
||||
// TODO: java runtime not found - should prob download
|
||||
|
||||
Err(JavaError::MissingJava.into())
|
||||
}
|
||||
|
||||
/// Attempts to scan common paths for Java installations.
|
||||
|
|
|
|||
|
|
@ -4,4 +4,5 @@ pub mod ingress;
|
|||
pub mod game;
|
||||
pub mod proxy;
|
||||
pub mod java;
|
||||
pub mod processes;
|
||||
pub mod setting_profiles;
|
||||
18
packages/core/src/api/processes.rs
Normal file
18
packages/core/src/api/processes.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::store::processes::Process;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub enum ProcessPayload {
|
||||
Started {
|
||||
process: Process,
|
||||
},
|
||||
Stopped {
|
||||
pid: u32,
|
||||
exit_code: i32,
|
||||
},
|
||||
Output {
|
||||
pid: u32,
|
||||
output: String,
|
||||
},
|
||||
}
|
||||
27
packages/core/src/api/proxy/event.rs
Normal file
27
packages/core/src/api/proxy/event.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::{error::LauncherResult, store::proxy::ProxyState};
|
||||
|
||||
#[onelauncher_macro::specta(with_event)]
|
||||
#[derive(Debug, Serialize)]
|
||||
pub enum LauncherEvent {
|
||||
Ingress(crate::api::ingress::IngressPayload),
|
||||
Message(crate::api::proxy::message::MessagePayload),
|
||||
Process(crate::api::processes::ProcessPayload),
|
||||
}
|
||||
|
||||
pub async fn send_event(event: LauncherEvent) {
|
||||
let Ok(proxy) = ProxyState::get() else {
|
||||
tracing::error!("failed to get proxy state");
|
||||
return;
|
||||
};
|
||||
|
||||
if let Err(e) = proxy.send_event(event).await {
|
||||
tracing::error!("failed to send event: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn try_send_event(event: LauncherEvent) -> LauncherResult<()> {
|
||||
let proxy = ProxyState::get()?;
|
||||
proxy.send_event(event).await
|
||||
}
|
||||
7
packages/core/src/api/proxy/impl/mod.rs
Normal file
7
packages/core/src/api/proxy/impl/mod.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#[cfg(feature = "tauri")] mod proxy_tauri;
|
||||
#[cfg(feature = "cli")] mod proxy_cli;
|
||||
mod proxy_empty;
|
||||
|
||||
#[cfg(feature = "tauri")] pub use proxy_tauri::ProxyTauri;
|
||||
#[cfg(feature = "cli")] pub use proxy_cli::ProxyCli;
|
||||
pub use proxy_empty::ProxyEmpty;
|
||||
60
packages/core/src/api/proxy/impl/proxy_cli.rs
Normal file
60
packages/core/src/api/proxy/impl/proxy_cli.rs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use indicatif::ProgressBar;
|
||||
use tokio::sync::RwLock;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{constants::CLI_TOTAL_INGRESS, api::proxy::{event::LauncherEvent, LauncherProxy}, LauncherResult};
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct ProxyCli {
|
||||
ingress_feeds: RwLock<HashMap<Uuid, ProgressBar>>,
|
||||
}
|
||||
|
||||
impl ProxyCli {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl LauncherProxy for ProxyCli {
|
||||
async fn send_event(&self, event: LauncherEvent) -> LauncherResult<()> {
|
||||
match event {
|
||||
LauncherEvent::Ingress(ingress) => {
|
||||
let mut feeds = self.ingress_feeds.write().await;
|
||||
|
||||
let completed = ingress.percent.is_none();
|
||||
|
||||
if let Some(progress) = feeds.get(&ingress.id) {
|
||||
if completed {
|
||||
progress.finish();
|
||||
} else {
|
||||
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
|
||||
let new_pos = (ingress.percent.unwrap() * CLI_TOTAL_INGRESS as f64).round() as u64;
|
||||
progress.set_position(new_pos);
|
||||
}
|
||||
} else if !completed {
|
||||
let progress = ProgressBar::new(CLI_TOTAL_INGRESS);
|
||||
progress.set_message(ingress.message.clone());
|
||||
progress.set_position(0);
|
||||
progress.set_style(indicatif::ProgressStyle::default_bar().template(
|
||||
"{spinner:.green}, [{elapsed_precise}] [{bar:.lime/green}] {pos}/{len} {msg}"
|
||||
).unwrap().progress_chars("#>-"));
|
||||
|
||||
feeds.insert(ingress.id, progress);
|
||||
}
|
||||
},
|
||||
LauncherEvent::Message(message) => {
|
||||
println!("[{:?}] {}", message.level, message.message);
|
||||
},
|
||||
_ => {
|
||||
println!("{:#?}", event);
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
48
packages/core/src/api/proxy/impl/proxy_empty.rs
Normal file
48
packages/core/src/api/proxy/impl/proxy_empty.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
use crate::{api::proxy::{event::LauncherEvent, LauncherProxy}, LauncherResult};
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct ProxyEmpty;
|
||||
|
||||
impl ProxyEmpty {
|
||||
#[must_use]
|
||||
pub const fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl LauncherProxy for ProxyEmpty {
|
||||
async fn send_event(&self, event: LauncherEvent) -> LauncherResult<()> {
|
||||
match event {
|
||||
LauncherEvent::Ingress(ingress) => {
|
||||
println!(
|
||||
"{} [{}] {}",
|
||||
ingress.percent.map_or("100%".to_string(), |p| format!("{:.2}%", p * 100.0)),
|
||||
ingress.id,
|
||||
ingress.message
|
||||
);
|
||||
},
|
||||
LauncherEvent::Message(message) => {
|
||||
println!("[{:?}] {}", message.level, message.message);
|
||||
},
|
||||
LauncherEvent::Process(payload) => {
|
||||
println!("{payload:#?}");
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "gui")]
|
||||
fn hide_main_window(&self) -> crate::LauncherResult<()> {
|
||||
println!("hidden window");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "gui")]
|
||||
fn show_main_window(&self) -> crate::LauncherResult<()> {
|
||||
println!("shown window");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -2,9 +2,7 @@ use tauri::{AppHandle, Emitter, Manager};
|
|||
use tauri_specta::Event;
|
||||
use tracing::{error, warn};
|
||||
|
||||
use crate::{store::ingress::IngressPayload, LauncherResult};
|
||||
|
||||
use super::{message::{MessageLevel, MessagePayload}, LauncherProxy};
|
||||
use crate::{constants::CLI_TOTAL_INGRESS, api::proxy::{event::LauncherEvent, LauncherProxy, message::MessageLevel}, LauncherResult};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ProxyTauri {
|
||||
|
|
@ -22,21 +20,20 @@ impl ProxyTauri {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
impl LauncherProxy for ProxyTauri {
|
||||
async fn send_ingress(&self, ingress: IngressPayload) -> LauncherResult<()> {
|
||||
self.handle
|
||||
.emit(IngressPayload::NAME, ingress)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn send_message(&self, message: MessagePayload) -> LauncherResult<()> {
|
||||
match message.level {
|
||||
MessageLevel::Info => {},
|
||||
MessageLevel::Warning => warn!("{}", message.message),
|
||||
MessageLevel::Error => error!("{}", message.message),
|
||||
};
|
||||
async fn send_event(&self, event: LauncherEvent) -> LauncherResult<()> {
|
||||
match event {
|
||||
LauncherEvent::Message(message) => {
|
||||
match message.level {
|
||||
MessageLevel::Info => {},
|
||||
MessageLevel::Warning => warn!("{}", message.message),
|
||||
MessageLevel::Error => error!("{}", message.message),
|
||||
};
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.handle
|
||||
.emit(MessagePayload::NAME, message)
|
||||
.emit_all(LauncherEvent::NAME, event)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use super::event::send_event;
|
||||
|
||||
#[onelauncher_macro::specta(with_event)]
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct MessagePayload {
|
||||
|
|
@ -11,48 +13,57 @@ pub struct MessagePayload {
|
|||
#[derive(Serialize, Debug, Clone)]
|
||||
pub enum MessageLevel {
|
||||
Info,
|
||||
Warning,
|
||||
Warn,
|
||||
Error,
|
||||
}
|
||||
|
||||
pub fn send_message(level: MessageLevel, message: String) {
|
||||
tokio::spawn(async move {
|
||||
let payload = MessagePayload {
|
||||
level,
|
||||
message,
|
||||
};
|
||||
|
||||
send_event(super::event::LauncherEvent::Message(payload)).await;
|
||||
});
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! send_message {
|
||||
($level:tt, $($message:tt)*) => {
|
||||
$crate::api::proxy::message::send_message(
|
||||
$level,
|
||||
format!($($message)*),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! send_info {
|
||||
($($message:tt)*) => {
|
||||
$crate::api::proxy::message::send_message(
|
||||
$crate::api::proxy::message::MessageLevel::Info,
|
||||
format!($($message)*),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! send_warning {
|
||||
($($message:tt)*) => {{
|
||||
tokio::spawn(async move {
|
||||
let _ = match $crate::store::proxy::ProxyState::get() {
|
||||
Ok(proxy) => {
|
||||
let payload = $crate::api::proxy::message::MessagePayload {
|
||||
level: $crate::api::proxy::message::MessageLevel::Warning,
|
||||
message: format!($($message)*),
|
||||
};
|
||||
let _ = proxy.send_message(payload).await;
|
||||
},
|
||||
Err(err) => {
|
||||
tracing::warn!($($message)*);
|
||||
tracing::warn!("failed to send warning: {}", err);
|
||||
},
|
||||
};
|
||||
})
|
||||
}};
|
||||
($($message:tt)*) => {
|
||||
$crate::api::proxy::message::send_message(
|
||||
$crate::api::proxy::message::MessageLevel::Warn,
|
||||
format!($($message)*),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! send_error {
|
||||
($($message:tt)*) => {{
|
||||
tokio::spawn(async move {
|
||||
let _ = match $crate::store::proxy::ProxyState::get() {
|
||||
Ok(proxy) => {
|
||||
let payload = $crate::api::proxy::message::MessagePayload {
|
||||
level: $crate::api::proxy::message::MessageLevel::Error,
|
||||
message: format!($($message)*),
|
||||
};
|
||||
let _ = proxy.send_message(payload).await;
|
||||
},
|
||||
Err(err) => {
|
||||
tracing::error!($($message)*);
|
||||
tracing::error!("failed to send error: {}", err);
|
||||
},
|
||||
};
|
||||
})
|
||||
}};
|
||||
($($message:tt)*) => {
|
||||
$crate::api::proxy::message::send_message(
|
||||
$crate::api::proxy::message::MessageLevel::Error,
|
||||
format!($($message)*),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,12 @@
|
|||
mod r#impl;
|
||||
pub mod message;
|
||||
pub mod event;
|
||||
|
||||
#[cfg(feature = "tauri")] mod proxy_tauri;
|
||||
#[cfg(feature = "cli")] mod proxy_cli;
|
||||
mod proxy_empty;
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[cfg(feature = "tauri")] pub use proxy_tauri::ProxyTauri;
|
||||
#[cfg(feature = "cli")] pub use proxy_cli::ProxyCli;
|
||||
pub use proxy_empty::ProxyEmpty;
|
||||
|
||||
#[cfg(feature = "cli")] pub type ProxyDynamic = ProxyCli;
|
||||
#[cfg(not(feature = "cli"))] pub type ProxyDynamic = ProxyEmpty;
|
||||
pub use r#impl::*;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait LauncherProxy: Send + Sync + Debug {
|
||||
async fn send_ingress(&self, ingress: crate::store::ingress::IngressPayload) -> crate::LauncherResult<()>;
|
||||
async fn send_message(&self, message: message::MessagePayload) -> crate::LauncherResult<()>;
|
||||
pub trait LauncherProxy: Send + Sync + std::fmt::Debug {
|
||||
async fn send_event(&self, event: event::LauncherEvent) -> crate::LauncherResult<()>;
|
||||
|
||||
#[cfg(feature = "gui")]
|
||||
fn hide_main_window(&self) -> crate::LauncherResult<()>;
|
||||
|
|
@ -24,3 +14,6 @@ pub trait LauncherProxy: Send + Sync + Debug {
|
|||
#[cfg(feature = "gui")]
|
||||
fn show_main_window(&self) -> crate::LauncherResult<()>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "cli")] pub type ProxyDynamic = ProxyCli;
|
||||
#[cfg(not(feature = "cli"))] pub type ProxyDynamic = ProxyEmpty;
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use indicatif::ProgressBar;
|
||||
use tokio::sync::RwLock;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{constants::CLI_TOTAL_INGRESS, store::ingress::IngressPayload, LauncherResult};
|
||||
|
||||
use super::LauncherProxy;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct ProxyCli {
|
||||
ingress_feeds: RwLock<HashMap<Uuid, ProgressBar>>,
|
||||
}
|
||||
|
||||
impl ProxyCli {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl LauncherProxy for ProxyCli {
|
||||
|
||||
async fn send_ingress(&self, ingress: IngressPayload) -> LauncherResult<()> {
|
||||
let mut feeds = self.ingress_feeds.write().await;
|
||||
|
||||
let completed = ingress.percent.is_none();
|
||||
|
||||
if let Some(progress) = feeds.get(&ingress.id) {
|
||||
if completed {
|
||||
progress.finish();
|
||||
} else {
|
||||
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
|
||||
let new_pos = (ingress.percent.unwrap() * CLI_TOTAL_INGRESS as f64).round() as u64;
|
||||
progress.set_position(new_pos);
|
||||
}
|
||||
} else if !completed {
|
||||
let progress = ProgressBar::new(CLI_TOTAL_INGRESS);
|
||||
progress.set_message(ingress.message.clone());
|
||||
progress.set_position(0);
|
||||
progress.set_style(indicatif::ProgressStyle::default_bar().template(
|
||||
"{spinner:.green}, [{elapsed_precise}] [{bar:.lime/green}] {pos}/{len} {msg}"
|
||||
).unwrap().progress_chars("#>-"));
|
||||
|
||||
feeds.insert(ingress.id, progress);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_message(&self, message: crate::api::proxy::message::MessagePayload) -> LauncherResult<()> {
|
||||
println!("[{:?}] {}", message.level, message.message);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
use crate::{store::ingress::IngressPayload, LauncherResult};
|
||||
|
||||
use super::LauncherProxy;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct ProxyEmpty;
|
||||
|
||||
impl ProxyEmpty {
|
||||
#[must_use]
|
||||
pub const fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl LauncherProxy for ProxyEmpty {
|
||||
|
||||
async fn send_ingress(&self, ingress: IngressPayload) -> LauncherResult<()> {
|
||||
println!(
|
||||
"{} [{}] {}",
|
||||
ingress.percent.map_or("100%".to_string(), |p| format!("{:.2}%", p * 100.0)),
|
||||
ingress.id,
|
||||
ingress.message
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_message(&self, message: crate::api::proxy::message::MessagePayload) -> LauncherResult<()> {
|
||||
println!("[{:?}] {}", message.level, message.message);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "gui")]
|
||||
fn hide_main_window(&self) -> crate::LauncherResult<()> {
|
||||
println!("hidden window");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "gui")]
|
||||
fn show_main_window(&self) -> crate::LauncherResult<()> {
|
||||
println!("shown window");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,60 +1,75 @@
|
|||
use onelauncher_entity::setting_profiles;
|
||||
use sea_orm::{ActiveModelTrait, EntityTrait};
|
||||
|
||||
use crate::{error::{DaoError, LauncherResult}, store::State};
|
||||
use crate::store::State;
|
||||
|
||||
pub async fn get_all_profiles() -> LauncherResult<Vec<setting_profiles::Model>> {
|
||||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
pub mod dao {
|
||||
use onelauncher_entity::setting_profiles;
|
||||
use sea_orm::{ActiveModelTrait, EntityTrait};
|
||||
|
||||
Ok(setting_profiles::Entity::find()
|
||||
.all(db)
|
||||
.await?)
|
||||
}
|
||||
use crate::{error::{DaoError, LauncherResult}, store::State};
|
||||
|
||||
pub async fn get_profile_or_default(name: Option<&String>) -> LauncherResult<setting_profiles::Model> {
|
||||
if let Some(name) = name {
|
||||
let profile = get_profile_by_name(name).await?;
|
||||
if let Some(profile) = profile {
|
||||
return Ok(profile);
|
||||
pub async fn get_all_profiles() -> LauncherResult<Vec<setting_profiles::Model>> {
|
||||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
|
||||
Ok(setting_profiles::Entity::find()
|
||||
.all(db)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn get_profile_or_default(name: Option<&String>) -> LauncherResult<setting_profiles::Model> {
|
||||
if let Some(name) = name {
|
||||
let profile = get_profile_by_name(name).await?;
|
||||
if let Some(profile) = profile {
|
||||
return Ok(profile);
|
||||
}
|
||||
}
|
||||
|
||||
let state = State::get().await?;
|
||||
let settings = &state.settings.read().await;
|
||||
Ok(settings.global_game_settings.clone())
|
||||
}
|
||||
|
||||
let state = State::get().await?;
|
||||
let settings = &state.settings.read().await;
|
||||
Ok(settings.global_game_settings.clone())
|
||||
}
|
||||
pub async fn get_profile_by_name(name: &str) -> LauncherResult<Option<setting_profiles::Model>> {
|
||||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
|
||||
pub async fn get_profile_by_name(name: &str) -> LauncherResult<Option<setting_profiles::Model>> {
|
||||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
Ok(setting_profiles::Entity::find_by_id(name)
|
||||
.one(db)
|
||||
.await?)
|
||||
}
|
||||
|
||||
Ok(setting_profiles::Entity::find_by_id(name)
|
||||
.one(db)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn insert_profile(profile: setting_profiles::ActiveModel) -> LauncherResult<setting_profiles::Model> {
|
||||
if let Some(name) = profile.name.try_as_ref() {
|
||||
if name.is_empty() || profile.is_global() {
|
||||
return Err(DaoError::InvalidValue(name.to_string(), "name".into()).into());
|
||||
pub async fn insert_profile(profile: setting_profiles::ActiveModel) -> LauncherResult<setting_profiles::Model> {
|
||||
if let Some(name) = profile.name.try_as_ref() {
|
||||
if name.is_empty() || profile.is_global() {
|
||||
return Err(DaoError::InvalidValue(name.to_string(), "name".into()).into());
|
||||
}
|
||||
}
|
||||
|
||||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
|
||||
Ok(profile.insert(db).await?)
|
||||
}
|
||||
|
||||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
pub async fn delete_profile_by_name(name: &str) -> LauncherResult<()> {
|
||||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
|
||||
Ok(profile.insert(db).await?)
|
||||
let deleted = setting_profiles::Entity::delete_by_id(name).exec(db).await?;
|
||||
|
||||
if deleted.rows_affected == 0 {
|
||||
return Err(DaoError::NotFound.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete_profile_by_name(name: &str) -> LauncherResult<()> {
|
||||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
pub async fn get_global_profile() -> setting_profiles::Model {
|
||||
let Ok(state) = State::get().await else {
|
||||
return setting_profiles::Model::default_global_profile();
|
||||
};
|
||||
|
||||
let deleted = setting_profiles::Entity::delete_by_id(name).exec(db).await?;
|
||||
|
||||
if deleted.rows_affected == 0 {
|
||||
return Err(DaoError::NotFound.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
let settings = state.settings.read().await;
|
||||
settings.global_game_settings.clone()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ pub enum LauncherError {
|
|||
#[error(transparent)]
|
||||
MinecraftAuthError(#[from] crate::store::credentials::MinecraftAuthError),
|
||||
#[error(transparent)]
|
||||
ProcessError(#[from] crate::store::processes::ProcessError),
|
||||
#[error(transparent)]
|
||||
DaoError(#[from] DaoError),
|
||||
|
||||
#[error(transparent)]
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@ use serde::Serialize;
|
|||
use tokio::sync::RwLock;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::api::proxy::event::try_send_event;
|
||||
use crate::LauncherResult;
|
||||
use crate::api::ingress::IngressPayload;
|
||||
|
||||
use super::State;
|
||||
use super::proxy::ProxyState;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct IngressProcessor {
|
||||
|
|
@ -73,21 +74,20 @@ impl IngressProcessor {
|
|||
let uuid = &id.0;
|
||||
let ingress = feeds.get_mut(uuid).ok_or(IngressError::NotFound)?;
|
||||
|
||||
let proxy = ProxyState::get()?;
|
||||
|
||||
ingress.current += increment;
|
||||
if let Some(message) = message {
|
||||
ingress.message = message;
|
||||
}
|
||||
|
||||
proxy
|
||||
.send_ingress(IngressPayload {
|
||||
id: uuid.to_owned(),
|
||||
message: ingress.message.clone(),
|
||||
ingress_type: ingress.ingress_type.clone(),
|
||||
percent: Some(ingress.current / ingress.total),
|
||||
total: ingress.total,
|
||||
})
|
||||
let payload = IngressPayload {
|
||||
id: uuid.to_owned(),
|
||||
message: ingress.message.clone(),
|
||||
ingress_type: ingress.ingress_type.clone(),
|
||||
percent: Some(ingress.current / ingress.total),
|
||||
total: ingress.total,
|
||||
};
|
||||
|
||||
try_send_event(crate::api::proxy::event::LauncherEvent::Ingress(payload))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
|
|
@ -113,20 +113,11 @@ pub struct Ingress {
|
|||
pub last_sent: f64,
|
||||
}
|
||||
|
||||
#[onelauncher_macro::specta(with_event)]
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct IngressPayload {
|
||||
pub id: Uuid,
|
||||
pub message: String,
|
||||
pub ingress_type: IngressType,
|
||||
pub percent: Option<f64>,
|
||||
pub total: f64,
|
||||
}
|
||||
|
||||
#[onelauncher_macro::specta]
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub enum IngressType {
|
||||
Download { file_name: String },
|
||||
JavaPrepare,
|
||||
JavaCheck,
|
||||
JavaLocate,
|
||||
MinecraftDownload,
|
||||
|
|
@ -182,7 +173,8 @@ impl SubIngressExt for SubIngress<'_> {
|
|||
|
||||
fn ingress_sub<F>(&self, total: F) -> Self::Target<'_>
|
||||
where
|
||||
F: FnOnce(f64) -> f64 {
|
||||
F: FnOnce(f64) -> f64,
|
||||
{
|
||||
SubIngress::from_sub(self, total(self.total))
|
||||
}
|
||||
}
|
||||
|
|
@ -201,7 +193,8 @@ impl SubIngressExt for Option<&SubIngress<'_>> {
|
|||
|
||||
fn ingress_sub<F>(&self, total: F) -> Self::Target<'_>
|
||||
where
|
||||
F: FnOnce(f64) -> f64 {
|
||||
F: FnOnce(f64) -> f64,
|
||||
{
|
||||
self.map(|sub| SubIngress::from_sub(sub, total(sub.total)))
|
||||
}
|
||||
}
|
||||
|
|
@ -217,16 +210,15 @@ impl Drop for IngressId {
|
|||
|
||||
let ingress = processor.remove(ingress_uuid).await?;
|
||||
|
||||
let proxy = ProxyState::get()?;
|
||||
let payload = IngressPayload {
|
||||
id: ingress.id,
|
||||
message: "Completed".into(),
|
||||
ingress_type: ingress.ingress_type,
|
||||
percent: None,
|
||||
total: ingress.total,
|
||||
};
|
||||
|
||||
proxy
|
||||
.send_ingress(IngressPayload {
|
||||
id: ingress.id,
|
||||
message: "Completed".into(),
|
||||
ingress_type: ingress.ingress_type,
|
||||
percent: None,
|
||||
total: ingress.total,
|
||||
})
|
||||
try_send_event(crate::api::proxy::event::LauncherEvent::Ingress(payload))
|
||||
.await?;
|
||||
|
||||
tracing::trace!(
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ pub mod ingress;
|
|||
pub mod proxy;
|
||||
pub mod semaphore;
|
||||
pub mod metadata;
|
||||
pub mod credentials;
|
||||
pub mod credentials;
|
||||
pub mod processes;
|
||||
mod core_options;
|
||||
mod dirs;
|
||||
mod settings;
|
||||
|
|
|
|||
276
packages/core/src/store/processes.rs
Normal file
276
packages/core/src/store/processes.rs
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
use std::collections::HashMap;
|
||||
use std::process::Stdio;
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use onelauncher_entity::setting_profiles;
|
||||
use serde::Serialize;
|
||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
use tokio::process::{Child, Command};
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::task::JoinHandle;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::api::cluster;
|
||||
use crate::api::game::log::censor_line;
|
||||
use crate::api::processes::ProcessPayload;
|
||||
use crate::api::proxy::event::{send_event, LauncherEvent};
|
||||
use crate::error::{DaoError, LauncherResult};
|
||||
use crate::send_error;
|
||||
use crate::utils::io::IOError;
|
||||
|
||||
use super::credentials::MinecraftCredentials;
|
||||
use super::State;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ProcessError {
|
||||
#[error("launch hook exited with non-zero code {0}")]
|
||||
HookUnsuccessful(i32),
|
||||
#[error("pid equaled nothing")]
|
||||
NoPid,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ProcessStore {
|
||||
processes: RwLock<HashMap<u32, Arc<RwLock<Process>>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Process {
|
||||
pub pid: u32,
|
||||
pub started_at: DateTime<Utc>,
|
||||
pub cluster_id: u64,
|
||||
pub post_hook: Option<String>,
|
||||
pub account_id: Uuid,
|
||||
#[serde(skip)]
|
||||
pub child_type: Arc<RwLock<ChildType>>,
|
||||
#[serde(skip)]
|
||||
process_loop: Option<JoinHandle<LauncherResult<i32>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub enum ChildType {
|
||||
Owned(#[serde(skip)] Child),
|
||||
}
|
||||
|
||||
impl Process {
|
||||
#[must_use]
|
||||
pub const fn is_running(&self) -> bool {
|
||||
self.process_loop.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl ChildType {
|
||||
#[must_use]
|
||||
pub const fn new(child: Child) -> Self {
|
||||
Self::Owned(child)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn id(&self) -> u32 {
|
||||
match self {
|
||||
Self::Owned(child) => child.id().unwrap_or(0),
|
||||
}
|
||||
}
|
||||
|
||||
// Attempts to return its exit code, if it has exited.
|
||||
pub fn try_wait(&mut self) -> LauncherResult<Option<i32>> {
|
||||
Ok(match self {
|
||||
Self::Owned(child) => child
|
||||
.try_wait()
|
||||
.map_err(IOError::from)?
|
||||
.map(|s| s.code().unwrap_or_default()),
|
||||
})
|
||||
}
|
||||
|
||||
// async blocking to wait for the process to finish and return exit code
|
||||
pub async fn wait(&mut self) -> LauncherResult<i32> {
|
||||
Ok(match self {
|
||||
Self::Owned(child) => child
|
||||
.wait()
|
||||
.await
|
||||
.map(|s| s.code().unwrap_or_default())
|
||||
.map_err(IOError::from)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn kill(&mut self) -> LauncherResult<()> {
|
||||
match self {
|
||||
Self::Owned(child) => child.kill().await.map_err(IOError::from)?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ProcessStore {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub async fn spawn(
|
||||
&self,
|
||||
cluster_id: u64,
|
||||
creds: MinecraftCredentials,
|
||||
settings: &setting_profiles::Model,
|
||||
mut command: Command,
|
||||
) -> LauncherResult<Arc<RwLock<Process>>> {
|
||||
command
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.stdin(Stdio::null());
|
||||
|
||||
let mut child = command.spawn().map_err(IOError::from)?;
|
||||
|
||||
let started_at = Utc::now();
|
||||
let Some(pid) = child.id() else {
|
||||
return Err(ProcessError::NoPid.into());
|
||||
};
|
||||
|
||||
let stdout = child.stdout.take().expect("couldn't get stdout of child");
|
||||
let stderr = child.stderr.take().expect("couldn't get stderr of child");
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if settings.os_extra.as_ref().is_some_and(|s| s.enable_gamemode.is_some_and(|x| x)) {
|
||||
if let Err(err) = onelauncher_gamemode::request_start_for_wrapper(pid) {
|
||||
tracing::warn!("failed to enable gamemode, continuing: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let account_id = creds.id;
|
||||
tokio::spawn(async move {
|
||||
let mut stdout = BufReader::new(stdout).lines();
|
||||
let mut stderr = BufReader::new(stderr).lines();
|
||||
|
||||
while let Ok(Some(line)) = tokio::select! {
|
||||
line = stdout.next_line() => line,
|
||||
line = stderr.next_line() => line,
|
||||
} {
|
||||
let censored = censor_line(&creds, line);
|
||||
|
||||
send_event(LauncherEvent::Process(ProcessPayload::Output {
|
||||
pid,
|
||||
output: censored,
|
||||
})).await;
|
||||
}
|
||||
});
|
||||
|
||||
let child_type = Arc::new(RwLock::new(ChildType::new(child)));
|
||||
let process = Process {
|
||||
pid,
|
||||
started_at,
|
||||
cluster_id,
|
||||
post_hook: settings.hook_post.clone(),
|
||||
account_id,
|
||||
child_type: child_type.clone(),
|
||||
process_loop: Some(tokio::task::spawn(Self::process_loop(
|
||||
cluster_id, settings.hook_post.clone(), child_type,
|
||||
))),
|
||||
};
|
||||
|
||||
let process = Arc::new(RwLock::new(process));
|
||||
let mut write_guard = self.processes.write().await;
|
||||
write_guard.insert(pid, process.clone());
|
||||
tracing::info!("spawned process with pid {}", pid);
|
||||
|
||||
Ok(process)
|
||||
}
|
||||
|
||||
pub async fn get(&self, pid: u32) -> Option<Arc<RwLock<Process>>> {
|
||||
let read_guard = self.processes.read().await;
|
||||
read_guard.get(&pid).cloned()
|
||||
}
|
||||
|
||||
async fn process_loop(
|
||||
cluster_id: u64,
|
||||
post_hook: Option<String>,
|
||||
child: Arc<RwLock<ChildType>>,
|
||||
) -> LauncherResult<i32> {
|
||||
let pid = child.read().await.id();
|
||||
let mut last_updated = Utc::now();
|
||||
|
||||
let exit_code = loop {
|
||||
let mut child = child.write().await;
|
||||
if let Some(status) = child.try_wait()? {
|
||||
tracing::info!("process exited with status: {:?}", status);
|
||||
break status;
|
||||
}
|
||||
|
||||
// Every 60 seconds, update the playtime of the process
|
||||
let duration = Utc::now().signed_duration_since(last_updated).num_seconds();
|
||||
if duration > 60 {
|
||||
last_updated = Utc::now();
|
||||
|
||||
if let Err(err) = cluster::update_playtime(cluster_id, duration).await {
|
||||
tracing::error!("failed to update playtime: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||
};
|
||||
|
||||
let state = State::get().await?;
|
||||
let store = &state.processes;
|
||||
|
||||
let mut write_guard = store.processes.write().await;
|
||||
|
||||
write_guard.remove(&pid);
|
||||
tracing::debug!("removed process with pid {}", pid);
|
||||
|
||||
if let Some(post_hook) = post_hook {
|
||||
if !post_hook.is_empty() {
|
||||
let result: LauncherResult<()> = {
|
||||
let cluster = cluster::dao::get_cluster_by_id(cluster_id)
|
||||
.await?
|
||||
.ok_or(DaoError::NotFound)?;
|
||||
|
||||
if let Err(err) = run_hook(&post_hook, &cluster.path).await {
|
||||
send_error!("{err:?}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
if let Err(err) = result {
|
||||
send_error!("failed to run post hook: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
send_event(LauncherEvent::Process(ProcessPayload::Stopped { pid, exit_code })).await;
|
||||
|
||||
Ok(exit_code)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_hook(hook: &str, cwd: &str) -> LauncherResult<Option<i32>> {
|
||||
let mut split = hook.split(' ');
|
||||
|
||||
let Some(command) = split.next() else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let result = Command::new(command)
|
||||
.args(split)
|
||||
.current_dir(cwd)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.map_err(IOError::from)?
|
||||
.wait()
|
||||
.await
|
||||
.map_err(IOError::from)?;
|
||||
|
||||
let exit_code = result
|
||||
.code()
|
||||
.unwrap_or_else(|| i32::from(!result.success()));
|
||||
|
||||
if result.success() {
|
||||
Ok(Some(exit_code))
|
||||
} else {
|
||||
Err(ProcessError::HookUnsuccessful(exit_code).into())
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ use tokio::sync::{OnceCell, RwLock};
|
|||
use crate::LauncherResult;
|
||||
|
||||
use super::credentials::CredentialsStore;
|
||||
use super::processes::ProcessStore;
|
||||
use super::Settings;
|
||||
use super::discord::DiscordRPC;
|
||||
use super::ingress::IngressProcessor;
|
||||
|
|
@ -20,6 +21,7 @@ pub struct State {
|
|||
pub db: DatabaseConnection,
|
||||
pub credentials: RwLock<CredentialsStore>,
|
||||
pub metadata: RwLock<Metadata>,
|
||||
pub processes: ProcessStore,
|
||||
pub rpc: Option<DiscordRPC>,
|
||||
}
|
||||
|
||||
|
|
@ -44,6 +46,7 @@ impl State {
|
|||
let db = super::db::create_pool().await?;
|
||||
let credentials = CredentialsStore::initialize().await?;
|
||||
let metadata = Metadata::new();
|
||||
let processes = ProcessStore::new();
|
||||
let rpc = match DiscordRPC::initialize() {
|
||||
Ok(rpc) => Some(rpc),
|
||||
Err(err) => {
|
||||
|
|
@ -58,6 +61,7 @@ impl State {
|
|||
db,
|
||||
credentials: RwLock::new(credentials),
|
||||
metadata: RwLock::new(metadata),
|
||||
processes,
|
||||
rpc,
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,4 +27,5 @@ chrono = { workspace = true }
|
|||
uuid = { workspace = true }
|
||||
url = { workspace = true }
|
||||
interpulse = { workspace = true }
|
||||
merge = { workspace = true }
|
||||
specta = { workspace = true, optional = true }
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use sea_orm::{DeriveActiveEnum, EnumIter};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
@ -11,4 +13,21 @@ pub enum ClusterStage {
|
|||
Downloading = 1,
|
||||
Repairing = 2,
|
||||
Ready = 3,
|
||||
}
|
||||
}
|
||||
|
||||
impl ClusterStage {
|
||||
pub fn is_downloading(&self) -> bool {
|
||||
matches!(self, Self::Downloading | Self::Repairing)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ClusterStage {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", match self {
|
||||
ClusterStage::NotReady => "Not Ready",
|
||||
ClusterStage::Downloading => "Downloading",
|
||||
ClusterStage::Repairing => "Repairing",
|
||||
ClusterStage::Ready => "Ready",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
pub mod cluster_stage;
|
||||
pub mod icon;
|
||||
pub mod loader;
|
||||
pub mod loader;
|
||||
pub mod settings;
|
||||
84
packages/entity/src/domain/settings.rs
Normal file
84
packages/entity/src/domain/settings.rs
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
use sea_orm::{sea_query::{self, Nullable, ValueType}, TryGetableFromJson};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
cfg_match! {
|
||||
target_os = "windows" => {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SettingsOsExtra {
|
||||
|
||||
}
|
||||
|
||||
impl Default for SettingsOsExtra {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
target_os = "macos" => {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SettingsOsExtra {
|
||||
|
||||
}
|
||||
|
||||
impl Default for SettingsOsExtra {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
not(any(target_os = "windows", target_os = "macos")) => {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SettingsOsExtra {
|
||||
pub enable_gamemode: Option<bool>,
|
||||
}
|
||||
|
||||
impl Default for SettingsOsExtra {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enable_gamemode: Some(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SettingsOsExtra> for sea_query::Value {
|
||||
fn from(value: SettingsOsExtra) -> Self {
|
||||
sea_query::Value::Json(serde_json::to_value(value).ok().map(Box::new))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryGetableFromJson for SettingsOsExtra {}
|
||||
|
||||
impl ValueType for SettingsOsExtra {
|
||||
fn try_from(v: sea_orm::Value) -> Result<Self, sea_query::ValueTypeErr> {
|
||||
match v {
|
||||
sea_orm::Value::Json(Some(json)) => Ok(
|
||||
serde_json::from_value(*json).map_err(|_| sea_orm::sea_query::ValueTypeErr)?,
|
||||
),
|
||||
_ => Err(sea_orm::sea_query::ValueTypeErr),
|
||||
}
|
||||
}
|
||||
|
||||
fn type_name() -> String {
|
||||
"SettingsOsExtra".to_string()
|
||||
}
|
||||
|
||||
fn array_type() -> sea_query::ArrayType {
|
||||
sea_query::ArrayType::Json
|
||||
}
|
||||
|
||||
fn column_type() -> sea_orm::ColumnType {
|
||||
sea_orm::ColumnType::Json
|
||||
}
|
||||
}
|
||||
|
||||
impl Nullable for SettingsOsExtra {
|
||||
fn null() -> sea_orm::Value {
|
||||
sea_orm::Value::Json(None)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
|
|||
#[onelauncher_macro::specta]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
pub id: u64,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub name: String,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
|
|||
#[onelauncher_macro::specta]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub cluster_id: i32,
|
||||
pub cluster_id: u64,
|
||||
#[sea_orm(primary_key, auto_increment = false, column_type = "Text")]
|
||||
pub package_hash: String,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,13 +8,12 @@ use crate::{cluster_stage::ClusterStage, icon::Icon, loader::GameLoader};
|
|||
#[onelauncher_macro::specta]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
pub id: u64,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub path: String,
|
||||
pub stage: ClusterStage,
|
||||
pub created_at: i32,
|
||||
pub updated_at: i32,
|
||||
pub group_id: Option<i32>,
|
||||
pub created_at: i64,
|
||||
pub group_id: Option<u64>,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub name: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
|
|
@ -22,15 +21,15 @@ pub struct Model {
|
|||
pub mc_loader: GameLoader,
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
pub mc_loader_version: Option<String>,
|
||||
pub last_played: Option<i32>,
|
||||
pub overall_played: Option<i32>,
|
||||
pub last_played: Option<i64>,
|
||||
pub overall_played: Option<i64>,
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
pub icon_url: Option<Icon>,
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
pub setting_profile_name: Option<String>,
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
pub linked_pack_id: Option<String>,
|
||||
pub linked_pack_version: Option<i32>,
|
||||
pub linked_pack_version: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ use serde::{Deserialize, Serialize};
|
|||
#[onelauncher_macro::specta]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
pub major_version: i32,
|
||||
pub id: u64,
|
||||
pub major_version: u32,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub vendor_name: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ pub struct Model {
|
|||
pub display_name: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub display_version: String,
|
||||
pub type_id: i32,
|
||||
pub provider_id: i32,
|
||||
pub type_id: u32,
|
||||
pub provider_id: u32,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub provider_version: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
|
|
|
|||
|
|
@ -1,8 +1,26 @@
|
|||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.5
|
||||
|
||||
pub use super::cluster_groups::Entity as ClusterGroups;
|
||||
pub use super::cluster_packages::Entity as ClusterPackages;
|
||||
pub use super::clusters::Entity as Clusters;
|
||||
pub use super::java_versions::Entity as JavaVersions;
|
||||
pub use super::packages::Entity as Packages;
|
||||
pub use super::setting_profiles::Entity as SettingProfiles;
|
||||
pub mod entity {
|
||||
pub use crate::cluster_groups::Entity as ClusterGroups;
|
||||
pub use crate::cluster_packages::Entity as ClusterPackages;
|
||||
pub use crate::clusters::Entity as Clusters;
|
||||
pub use crate::java_versions::Entity as JavaVersions;
|
||||
pub use crate::packages::Entity as Packages;
|
||||
pub use crate::setting_profiles::Entity as SettingProfiles;
|
||||
}
|
||||
|
||||
pub mod model {
|
||||
pub use crate::cluster_groups::Model as ClusterGroup;
|
||||
pub use crate::cluster_packages::Model as ClusterPackage;
|
||||
pub use crate::clusters::Model as Cluster;
|
||||
pub use crate::java_versions::Model as JavaVersion;
|
||||
pub use crate::packages::Model as Package;
|
||||
pub use crate::setting_profiles::Model as SettingsProfile;
|
||||
|
||||
pub use crate::cluster_groups::ActiveModel as ClusterGroupPartial;
|
||||
pub use crate::cluster_packages::ActiveModel as ClusterPackagePartial;
|
||||
pub use crate::clusters::ActiveModel as ClusterPartial;
|
||||
pub use crate::java_versions::ActiveModel as JavaVersionPartial;
|
||||
pub use crate::packages::ActiveModel as PackagePartial;
|
||||
pub use crate::setting_profiles::ActiveModel as SettingsProfilePartial;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,24 @@
|
|||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.5
|
||||
|
||||
use merge::Merge;
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||
use crate::settings::SettingsOsExtra;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize, Merge)]
|
||||
#[sea_orm(table_name = "setting_profiles")]
|
||||
#[onelauncher_macro::specta]
|
||||
#[merge(strategy = merge::option::overwrite_none)]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false, column_type = "Text")]
|
||||
#[merge(skip)]
|
||||
pub name: String,
|
||||
pub java_id: Option<i32>,
|
||||
pub res_w: Option<i32>,
|
||||
pub res_h: Option<i32>,
|
||||
pub java_id: Option<u64>,
|
||||
pub res_w: Option<u32>,
|
||||
pub res_h: Option<u32>,
|
||||
pub force_fullscreen: Option<bool>,
|
||||
pub mem_max: Option<i32>,
|
||||
pub mem_max: Option<u32>,
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
pub launch_args: Option<String>,
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
|
|
@ -24,6 +29,8 @@ pub struct Model {
|
|||
pub hook_wrapper: Option<String>,
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
pub hook_post: Option<String>,
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
pub os_extra: Option<SettingsOsExtra>
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
|
|
@ -80,6 +87,7 @@ impl Model {
|
|||
mem_max: Some(3072),
|
||||
res_h: None,
|
||||
res_w: None,
|
||||
os_extra: Some(SettingsOsExtra::default())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#![feature(cfg_match)]
|
||||
|
||||
mod entities;
|
||||
mod domain;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,20 @@ mod pin;
|
|||
|
||||
/// This macro is used to derive the `specta::Type` trait for the annotated item.
|
||||
/// It's mainly meant for entity-generation with sea-orm-cli (it doesn't like commas in attributes)
|
||||
/// <br>
|
||||
/// Expands to:
|
||||
///
|
||||
/// #### Expands to:
|
||||
/// ```
|
||||
/// #[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
/// ```
|
||||
///
|
||||
/// ## `with_event` argument
|
||||
/// Add `#[specta(with_event)]` to add the `tauri_specta::Event` derive.
|
||||
///
|
||||
/// #### Expands to:
|
||||
/// ```
|
||||
/// #[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
/// #[cfg_attr(feature = "tauri", derive(tauri_specta::Event))]
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn specta(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
specta::specta(attr, item)
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ create table setting_profiles (
|
|||
hook_pre text,
|
||||
hook_wrapper text,
|
||||
hook_post text,
|
||||
os_extra json_text,
|
||||
constraint setting_profiles_pk primary key (name),
|
||||
constraint setting_profiles_java_versions_id_fk foreign key (java_id) references java_versions(id)
|
||||
);
|
||||
|
|
@ -51,7 +52,6 @@ create table clusters (
|
|||
path text not null,
|
||||
stage integer default (0) not null,
|
||||
created_at integer default (unixepoch()) not null,
|
||||
updated_at integer default (unixepoch()) not null,
|
||||
group_id integer,
|
||||
name text not null,
|
||||
mc_version text not null,
|
||||
|
|
@ -62,7 +62,7 @@ create table clusters (
|
|||
icon_url text,
|
||||
setting_profile_name text,
|
||||
linked_pack_id text,
|
||||
linked_pack_version integer,
|
||||
linked_pack_version text,
|
||||
constraint clusters_pk primary key (id autoincrement),
|
||||
constraint clusters_cluster_groups_id_fk foreign key (group_id) references cluster_groups(id),
|
||||
constraint clusters_setting_profiles_name_fk foreign key (setting_profile_name) references setting_profiles(name)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue