feat: Lots of small tweaks and stuff, begin work on minecraft / interpulse metadata
This commit is contained in:
parent
2f7c26229a
commit
54d16e5d69
19 changed files with 375 additions and 238 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -2954,6 +2954,7 @@ dependencies = [
|
|||
"onelauncher_gamemode",
|
||||
"onelauncher_macro",
|
||||
"onelauncher_migration",
|
||||
"paste",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"reqwest",
|
||||
|
|
|
|||
|
|
@ -150,6 +150,7 @@ libc = { version = "0.2" }
|
|||
# macro
|
||||
syn = { version = "2" }
|
||||
quote = { version = "1" }
|
||||
paste = { version = "1" }
|
||||
|
||||
[workspace.lints.clippy]
|
||||
all = { level = "warn", priority = -1 }
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ whoami = { workspace = true }
|
|||
rand = { workspace = true }
|
||||
url = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
|
||||
# integration
|
||||
discord-rich-presence = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use onelauncher_core::{api::{self, proxy::proxy_empty::ProxyEmpty}, error::LauncherResult, initialize_core, store::CoreOptions};
|
||||
use onelauncher_core::{api::{self, proxy::ProxyEmpty}, error::LauncherResult, initialize_core, store::CoreOptions};
|
||||
use onelauncher_entity::loader::GameLoader;
|
||||
|
||||
#[tokio::main]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use discord_rich_presence::activity::Timestamps;
|
||||
use onelauncher_core::{api::proxy::proxy_empty::ProxyEmpty, error::LauncherResult, initialize_core, store::{CoreOptions, State}};
|
||||
use onelauncher_core::{api::proxy::ProxyEmpty, error::LauncherResult, initialize_core, store::{CoreOptions, State}};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> LauncherResult<()> {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use onelauncher_core::{api::{java, proxy::proxy_empty::ProxyEmpty}, error::LauncherResult, initialize_core, store::CoreOptions};
|
||||
use onelauncher_core::{api::{java, proxy::ProxyEmpty}, error::LauncherResult, initialize_core, store::CoreOptions};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> LauncherResult<()> {
|
||||
|
|
|
|||
|
|
@ -1,18 +1,16 @@
|
|||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use onelauncher_entity::clusters;
|
||||
use onelauncher_entity::icon::Icon;
|
||||
use onelauncher_entity::loader::GameLoader;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::error::{DaoError, LauncherResult};
|
||||
use crate::send_error;
|
||||
use crate::error::LauncherResult;
|
||||
use crate::store::Dirs;
|
||||
use crate::utils::io;
|
||||
|
||||
pub mod dao;
|
||||
|
||||
mod sync;
|
||||
pub use sync::*;
|
||||
|
||||
#[must_use]
|
||||
pub fn sanitize_name(name: &str) -> String {
|
||||
let mut name = name.to_string();
|
||||
|
|
@ -29,7 +27,7 @@ pub async fn create_cluster(
|
|||
icon_url: Option<Icon>,
|
||||
) -> LauncherResult<clusters::Model> {
|
||||
let name = sanitize_name(name);
|
||||
let cluster_dir = Dirs::get().await?.clusters_dir();
|
||||
let cluster_dir = Dirs::get_clusters_dir().await?;
|
||||
|
||||
// Get the directory for the cluster
|
||||
let mut path = cluster_dir.join(&name);
|
||||
|
|
@ -81,109 +79,4 @@ pub async fn create_cluster(
|
|||
}
|
||||
}
|
||||
|
||||
#[onelauncher_macro::specta]
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
|
||||
pub enum SyncAction {
|
||||
Created,
|
||||
Updated,
|
||||
MissingDb,
|
||||
MissingFs,
|
||||
NoAction,
|
||||
}
|
||||
|
||||
impl SyncAction {
|
||||
#[must_use]
|
||||
pub const fn is_missing(self) -> bool {
|
||||
matches!(self, Self::MissingDb | Self::MissingFs)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>> {
|
||||
let clusters = dao::get_all_clusters().await?;
|
||||
|
||||
let mut missing_ids = Vec::new();
|
||||
let mut checked_paths = HashSet::new();
|
||||
|
||||
for cluster in clusters {
|
||||
if sync_cluster(&cluster)
|
||||
.await
|
||||
.map(SyncAction::is_missing)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
missing_ids.push(cluster.id);
|
||||
|
||||
let path = cluster.path.clone();
|
||||
send_error!(
|
||||
"cluster {} is missing from the filesystem",
|
||||
path.clone()
|
||||
);
|
||||
}
|
||||
|
||||
checked_paths.insert(PathBuf::from(cluster.path));
|
||||
}
|
||||
|
||||
let cluster_dir = Dirs::get().await?.clusters_dir();
|
||||
let mut stream = io::read_dir(cluster_dir).await?;
|
||||
while let Ok(Some(entry)) = stream.next_entry().await {
|
||||
let path = entry.path();
|
||||
if !path.is_dir() || checked_paths.contains(&path) {
|
||||
// Skip if it's not a directory or if we've already checked it
|
||||
continue;
|
||||
}
|
||||
|
||||
// We have a directory that is not in the database
|
||||
if let Err(err) = sync_from_fs_to_db(&path).await {
|
||||
tracing::error!("failed to sync cluster from fs to db: {}", err);
|
||||
};
|
||||
}
|
||||
|
||||
Ok(missing_ids)
|
||||
}
|
||||
|
||||
/// 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(cluster: &clusters::Model) -> LauncherResult<SyncAction> {
|
||||
let path = PathBuf::from(cluster.path.clone());
|
||||
if !path.exists() {
|
||||
return Ok(SyncAction::MissingFs);
|
||||
}
|
||||
|
||||
// TODO: sync packages
|
||||
|
||||
Ok(SyncAction::NoAction)
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
let cluster = dao::get_cluster_by_id(id)
|
||||
.await?
|
||||
.ok_or(DaoError::NotFound)?;
|
||||
|
||||
sync_cluster(&cluster).await
|
||||
}
|
||||
|
||||
/// 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_path(path: &PathBuf) -> LauncherResult<SyncAction> {
|
||||
let Some(cluster) = dao::get_cluster_by_path(path).await? else {
|
||||
tracing::warn!("cluster with path {path:?} not found in database");
|
||||
return Ok(SyncAction::MissingDb);
|
||||
};
|
||||
|
||||
sync_cluster(&cluster).await
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
async fn sync_from_fs_to_db(path: &PathBuf) -> LauncherResult<clusters::Model> {
|
||||
// TODO: Create database entry from what can be inferred from the directory
|
||||
Err(anyhow::anyhow!(
|
||||
"TODO: failed to sync cluster from fs to db: {}",
|
||||
path.display()
|
||||
).into())
|
||||
}
|
||||
|
|
|
|||
118
packages/core/src/api/cluster/sync.rs
Normal file
118
packages/core/src/api/cluster/sync.rs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
use std::{collections::HashSet, path::PathBuf};
|
||||
|
||||
use onelauncher_entity::clusters;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{error::{DaoError, LauncherResult}, send_error, store::Dirs, utils::io};
|
||||
|
||||
use super::dao;
|
||||
|
||||
#[onelauncher_macro::specta]
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
|
||||
pub enum SyncAction {
|
||||
Created,
|
||||
Updated,
|
||||
MissingDb,
|
||||
MissingFs,
|
||||
NoAction,
|
||||
}
|
||||
|
||||
impl SyncAction {
|
||||
#[must_use]
|
||||
pub const fn is_missing(self) -> bool {
|
||||
matches!(self, Self::MissingDb | Self::MissingFs)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>> {
|
||||
let clusters = dao::get_all_clusters().await?;
|
||||
|
||||
let mut missing_ids = Vec::new();
|
||||
let mut checked_paths = HashSet::new();
|
||||
|
||||
for cluster in clusters {
|
||||
if sync_cluster(&cluster)
|
||||
.await
|
||||
.map(SyncAction::is_missing)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
missing_ids.push(cluster.id);
|
||||
|
||||
let path = cluster.path.clone();
|
||||
send_error!(
|
||||
"cluster {} is missing from the filesystem",
|
||||
path.clone()
|
||||
);
|
||||
}
|
||||
|
||||
checked_paths.insert(PathBuf::from(cluster.path));
|
||||
}
|
||||
|
||||
// Check for clusters in the filesystem that are not in the database
|
||||
// It iterates over the directories in the cluster directory
|
||||
// and checks if they are in the database
|
||||
let cluster_dir = Dirs::get_clusters_dir().await?;
|
||||
let mut stream = io::read_dir(cluster_dir).await?;
|
||||
while let Ok(Some(entry)) = stream.next_entry().await {
|
||||
let path = entry.path();
|
||||
if !path.is_dir() || checked_paths.contains(&path) {
|
||||
// Skip if it's not a directory or if we've already checked it
|
||||
continue;
|
||||
}
|
||||
|
||||
// We have a directory that is not in the database
|
||||
if let Err(err) = sync_from_fs_to_db(&path).await {
|
||||
tracing::error!("failed to sync cluster from fs to db: {}", err);
|
||||
};
|
||||
}
|
||||
|
||||
Ok(missing_ids)
|
||||
}
|
||||
|
||||
/// 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(cluster: &clusters::Model) -> LauncherResult<SyncAction> {
|
||||
let path = PathBuf::from(cluster.path.clone());
|
||||
if !path.exists() {
|
||||
return Ok(SyncAction::MissingFs);
|
||||
}
|
||||
|
||||
// TODO: sync packages
|
||||
|
||||
Ok(SyncAction::NoAction)
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
let cluster = dao::get_cluster_by_id(id)
|
||||
.await?
|
||||
.ok_or(DaoError::NotFound)?;
|
||||
|
||||
sync_cluster(&cluster).await
|
||||
}
|
||||
|
||||
/// 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_path(path: &PathBuf) -> LauncherResult<SyncAction> {
|
||||
let Some(cluster) = dao::get_cluster_by_path(path).await? else {
|
||||
tracing::warn!("cluster with path {path:?} not found in database");
|
||||
return Ok(SyncAction::MissingDb);
|
||||
};
|
||||
|
||||
sync_cluster(&cluster).await
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
async fn sync_from_fs_to_db(path: &PathBuf) -> LauncherResult<clusters::Model> {
|
||||
// TODO: Create database entry from what can be inferred from the directory
|
||||
Err(anyhow::anyhow!(
|
||||
"TODO: failed to sync cluster from fs to db: {}",
|
||||
path.display()
|
||||
).into())
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{store::{ingress::{IngressId, IngressType}, State}, LauncherResult};
|
||||
use crate::{store::{ingress::{IngressId, IngressRef, IngressType}, State}, LauncherResult};
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn init_ingress(
|
||||
|
|
@ -14,16 +14,6 @@ pub async fn init_ingress(
|
|||
Ok(id)
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn send_ingress(id: &IngressId, increment: f64) -> LauncherResult<()> {
|
||||
let state = State::get().await?;
|
||||
let processor = &state.ingress_processor;
|
||||
|
||||
processor.send(id, increment).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn init_ingress_opt(
|
||||
init: bool,
|
||||
ingress_type: IngressType,
|
||||
|
|
@ -39,27 +29,47 @@ pub async fn init_ingress_opt(
|
|||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn send_ingress_opt(
|
||||
id: Option<&IngressId>,
|
||||
increment: f64,
|
||||
) -> LauncherResult<()> {
|
||||
if let Some(id) = id {
|
||||
send_ingress(id, increment).await?;
|
||||
}
|
||||
pub async fn send_ingress(id: impl AsRef<IngressId> + std::fmt::Debug, increment: f64) -> LauncherResult<()> {
|
||||
let state = State::get().await?;
|
||||
let processor = &state.ingress_processor;
|
||||
|
||||
processor.send(id, increment).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "cli", test))]
|
||||
pub mod tests {
|
||||
use crate::store::CoreOptions;
|
||||
pub async fn send_ingress_opt(id: Option<impl AsRef<IngressId> + std::fmt::Debug>, increment: f64) -> LauncherResult<()> {
|
||||
if let Some(id) = id {
|
||||
send_ingress(id, increment).await
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_ingress_ref(ingress_ref: &IngressRef<'_>) -> LauncherResult<()> {
|
||||
let state = State::get().await?;
|
||||
let processor = &state.ingress_processor;
|
||||
|
||||
processor.send(ingress_ref, ingress_ref.increment_by).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn send_ingress_ref_opt(ingress_ref: Option<&IngressRef<'_>>) -> LauncherResult<()> {
|
||||
if let Some(ingress_ref) = ingress_ref {
|
||||
send_ingress_ref(ingress_ref).await
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use std::time::Duration;
|
||||
use crate::{api::{ingress::{init_ingress, send_ingress}, proxy::ProxyDynamic}, initialize_core, store::{ingress::IngressType, CoreOptions}};
|
||||
|
||||
#[tokio::test]
|
||||
pub async fn create_and_update_ingress() -> crate::LauncherResult<()> {
|
||||
use std::time::Duration;
|
||||
use crate::{api::{ingress::{init_ingress, send_ingress}, proxy::proxy_cli::ProxyCli}, initialize_core, store::ingress::IngressType};
|
||||
|
||||
initialize_core(CoreOptions::default(), ProxyCli::new()).await?;
|
||||
initialize_core(CoreOptions::default(), ProxyDynamic::new()).await?;
|
||||
|
||||
let id = init_ingress(IngressType::Download { file_name: "Some-Mod-1.8.9.jar".into() }, "This is a test message", 100.0).await?;
|
||||
tokio::time::sleep(Duration::from_millis(2350)).await;
|
||||
|
|
|
|||
|
|
@ -48,13 +48,14 @@ const JAVA_INFO_CLASS: &[u8] = include_bytes!("../../../assets/java/JavaInfo.cla
|
|||
/// Accepts a path to a java executable and returns the [`JavaInfo`]
|
||||
pub async fn check_java_runtime(absolute_path: &PathBuf, with_ingress: bool) -> LauncherResult<JavaInfo> {
|
||||
let id = init_ingress_opt(with_ingress, IngressType::JavaCheck, "Checking JRE information", 100.0).await?;
|
||||
let id = id.as_ref();
|
||||
|
||||
let dir = io::tempdir()?;
|
||||
let file = dir.path().join("JavaInfo.class");
|
||||
send_ingress_opt(id.as_ref(), 25.0).await?;
|
||||
send_ingress_opt(id, 25.0).await?;
|
||||
|
||||
io::write(&file, JAVA_INFO_CLASS).await?;
|
||||
send_ingress_opt(id.as_ref(), 25.0).await?;
|
||||
send_ingress_opt(id, 25.0).await?;
|
||||
|
||||
let java_info = tokio::process::Command::new(absolute_path)
|
||||
.arg("-cp")
|
||||
|
|
@ -66,7 +67,7 @@ pub async fn check_java_runtime(absolute_path: &PathBuf, with_ingress: bool) ->
|
|||
.map_err(JavaError::from)?;
|
||||
|
||||
let java_info = String::from_utf8_lossy(&java_info.stdout);
|
||||
send_ingress_opt(id.as_ref(), 50.0).await?;
|
||||
send_ingress_opt(id, 50.0).await?;
|
||||
|
||||
let info = java_info.lines()
|
||||
.map(|line| {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
pub mod message;
|
||||
|
||||
#[cfg(feature = "tauri")]
|
||||
pub mod proxy_tauri;
|
||||
#[cfg(feature = "tauri")] mod proxy_tauri;
|
||||
#[cfg(feature = "cli")] mod proxy_cli;
|
||||
mod proxy_empty;
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
pub mod proxy_cli;
|
||||
#[cfg(feature = "tauri")] pub use proxy_tauri::ProxyTauri;
|
||||
#[cfg(feature = "cli")] pub use proxy_cli::ProxyCli;
|
||||
pub use proxy_empty::ProxyEmpty;
|
||||
|
||||
pub mod proxy_empty;
|
||||
#[cfg(feature = "cli")] pub type ProxyDynamic = ProxyCli;
|
||||
#[cfg(not(feature = "cli"))] pub type ProxyDynamic = ProxyEmpty;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait LauncherProxy: Send + Sync {
|
||||
|
|
|
|||
73
packages/core/src/game/metadata.rs
Normal file
73
packages/core/src/game/metadata.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
use interpulse::api::{minecraft::{Version, VersionInfo}, modded::LoaderVersion};
|
||||
|
||||
use crate::{api::ingress::{init_ingress, send_ingress_ref_opt}, error::LauncherResult, store::{ingress::{IngressRef, IngressType}, Dirs}, utils::{http::fetch_json_advanced, io}};
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn download_minecraft(version: &VersionInfo) -> LauncherResult<()> {
|
||||
let id = init_ingress(IngressType::MinecraftDownload, "Downloading Minecraft", 100.0).await?;
|
||||
|
||||
// tokio::try_join! {
|
||||
|
||||
// }?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn download_version_info(
|
||||
version: &Version,
|
||||
loader: Option<&LoaderVersion>,
|
||||
force: Option<bool>,
|
||||
ingress_ref: Option<&IngressRef<'_>>,
|
||||
) -> LauncherResult<VersionInfo> {
|
||||
let version_id = loader.map_or(version.id.clone(), |it| format!("{}-{}", version.id, it.id));
|
||||
|
||||
let ingress_ref = ingress_ref.map(|i| i.with_increment(i.increment_by / 3.33));
|
||||
let ingress_ref = ingress_ref.as_ref();
|
||||
|
||||
tracing::debug!("loading minecraft version info for minecraft version {version_id}");
|
||||
|
||||
let path = Dirs::get_version_infos_dir().await?.join(format!("{version_id}.json"));
|
||||
|
||||
let result = if path.exists() && !force.unwrap_or(false) {
|
||||
let data = io::read(path).await?;
|
||||
serde_json::from_slice(&data)?
|
||||
} else {
|
||||
tracing::info!(
|
||||
"downloading minecraft version info for minecraft version {}",
|
||||
&version_id
|
||||
);
|
||||
|
||||
let mut info = fetch_json_advanced(
|
||||
reqwest::Method::GET,
|
||||
&version.url,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
ingress_ref
|
||||
).await?;
|
||||
|
||||
if let Some(loader) = loader {
|
||||
let partial: interpulse::api::modded::PartialVersionInfo = fetch_json_advanced(
|
||||
reqwest::Method::GET,
|
||||
&loader.url,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
ingress_ref
|
||||
).await?;
|
||||
|
||||
info = interpulse::api::modded::merge_partial_version(partial, info);
|
||||
}
|
||||
|
||||
info.id.clone_from(&version_id);
|
||||
|
||||
io::write(&path, &serde_json::to_vec(&info)?).await?;
|
||||
info
|
||||
};
|
||||
|
||||
send_ingress_ref_opt(ingress_ref).await?;
|
||||
|
||||
tracing::debug!("loaded minecraft version info for minecraft version {version_id}");
|
||||
Ok(result)
|
||||
}
|
||||
1
packages/core/src/game/mod.rs
Normal file
1
packages/core/src/game/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub mod metadata;
|
||||
|
|
@ -3,14 +3,16 @@ use store::{proxy::ProxyState, Core, CoreOptions, Dirs, State};
|
|||
use error::LauncherResult;
|
||||
use logger::start_logger;
|
||||
|
||||
pub mod constants;
|
||||
pub mod utils;
|
||||
pub mod store;
|
||||
pub mod api;
|
||||
pub mod constants;
|
||||
pub mod error;
|
||||
pub mod game;
|
||||
pub mod store;
|
||||
pub mod utils;
|
||||
|
||||
mod logger;
|
||||
|
||||
pub use onelauncher_macro as macros;
|
||||
pub use onelauncher_entity as entity;
|
||||
pub use onelauncher_migration as migration;
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,6 @@ pub async fn create_pool_from_path(path: PathBuf) -> LauncherResult<DatabaseConn
|
|||
|
||||
#[tracing::instrument]
|
||||
pub async fn create_pool() -> LauncherResult<DatabaseConnection> {
|
||||
let path = Dirs::get().await?.db_file();
|
||||
let path = Dirs::get_db_file().await?;
|
||||
create_pool_from_path(path).await
|
||||
}
|
||||
|
|
@ -14,6 +14,12 @@ pub struct Dirs {
|
|||
base_dir: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum DirectoryError {
|
||||
#[error("Failed to get the base directory for the launcher.")]
|
||||
BaseDir,
|
||||
}
|
||||
|
||||
impl Dirs {
|
||||
pub async fn get() -> LauncherResult<&'static Self> {
|
||||
DIRS_GLOBAL.get_or_try_init(async || {
|
||||
|
|
@ -37,68 +43,41 @@ impl Dirs {
|
|||
&self.base_dir
|
||||
}
|
||||
|
||||
/// Get the launcher's logs directory.
|
||||
#[must_use]
|
||||
pub fn launcher_logs_dir(&self) -> PathBuf {
|
||||
self.base_dir().join("logs")
|
||||
}
|
||||
|
||||
|
||||
// --- FILES ---
|
||||
|
||||
#[must_use]
|
||||
pub fn db_file(&self) -> PathBuf {
|
||||
self.base_dir().join("user_data.db")
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn settings_file(&self) -> PathBuf {
|
||||
self.base_dir().join("settings.json")
|
||||
}
|
||||
|
||||
// --- GAME DIRECTORIES ---
|
||||
|
||||
/// Get the `config_dir/clusters` folder for the clusters.
|
||||
#[must_use]
|
||||
pub fn clusters_dir(&self) -> PathBuf {
|
||||
self.base_dir().join("clusters")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata` folder within the core config directory.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn metadata_dir(&self) -> PathBuf {
|
||||
self.base_dir().join("metadata")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/libraries` folder for Minecraft libraries.
|
||||
#[must_use]
|
||||
pub fn libraries_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().join("libraries")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/natives` folder for Minecraft natives.
|
||||
#[must_use]
|
||||
pub fn natives_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().join("natives")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/java` directory.
|
||||
#[must_use]
|
||||
pub fn java_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().join("java")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/caches` directory.
|
||||
#[must_use]
|
||||
pub fn caches_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().join("caches")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum DirectoryError {
|
||||
#[error("Failed to get the base directory for the launcher.")]
|
||||
BaseDir,
|
||||
macro_rules! dirs_impl {
|
||||
($($name:ident => |$self:ident| $path:expr),*) => {
|
||||
paste::paste! {
|
||||
impl Dirs {
|
||||
$(
|
||||
#[must_use]
|
||||
pub fn $name(&self) -> PathBuf {
|
||||
let $self = self;
|
||||
$path
|
||||
}
|
||||
|
||||
pub async fn [<get _ $name>]() -> LauncherResult<PathBuf> {
|
||||
Dirs::get().await.map(|$self| {
|
||||
$self.$name()
|
||||
})
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
dirs_impl! {
|
||||
db_file => |this| this.base_dir.join("user_data.db"),
|
||||
settings_file => |this| this.base_dir.join("settings.json"),
|
||||
|
||||
launcher_logs_dir => |this| this.base_dir.join("logs"),
|
||||
clusters_dir => |this| this.base_dir.join("clusters"),
|
||||
metadata_dir => |this| this.base_dir.join("metadata"),
|
||||
|
||||
version_infos_dir => |this| this.metadata_dir().join("version_infos"),
|
||||
libraries_dir => |this| this.metadata_dir().join("libraries"),
|
||||
natives_dir => |this| this.metadata_dir().join("natives"),
|
||||
java_dir => |this| this.metadata_dir().join("java"),
|
||||
caches_dir => |this| this.metadata_dir().join("caches")
|
||||
}
|
||||
|
|
@ -57,9 +57,9 @@ impl IngressProcessor {
|
|||
Ok(ingress_id)
|
||||
}
|
||||
|
||||
pub async fn send(&self, id: &IngressId, increment: f64) -> LauncherResult<()> {
|
||||
pub async fn send(&self, id: impl AsRef<IngressId>, increment: f64) -> LauncherResult<()> {
|
||||
let mut feeds = self.ingress_feeds.write().await;
|
||||
let uuid = &id.0;
|
||||
let uuid = &id.as_ref().0;
|
||||
let ingress = feeds.get_mut(uuid).ok_or(IngressError::NotFound)?;
|
||||
|
||||
let proxy = ProxyState::get()?;
|
||||
|
|
@ -86,6 +86,43 @@ impl IngressProcessor {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct IngressId(pub Uuid);
|
||||
|
||||
impl AsRef<Self> for IngressId {
|
||||
fn as_ref(&self) -> &Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to pass around an ingress id and the increment value, usually passed in helper / utility functions
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IngressRef<'a> {
|
||||
pub ingress_id: &'a IngressId,
|
||||
pub increment_by: f64,
|
||||
}
|
||||
|
||||
impl<'a> IngressRef<'a> {
|
||||
#[must_use]
|
||||
pub const fn new(ingress_id: &'a IngressId, increment_by: f64) -> Self {
|
||||
Self {
|
||||
ingress_id,
|
||||
increment_by,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn with_increment(&self, increment_by: f64) -> Self {
|
||||
Self {
|
||||
ingress_id: self.ingress_id,
|
||||
increment_by,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<IngressId> for IngressRef<'_> {
|
||||
fn as_ref(&self) -> &IngressId {
|
||||
self.ingress_id
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Ingress {
|
||||
pub id: Uuid,
|
||||
|
|
@ -113,7 +150,8 @@ pub enum IngressType {
|
|||
file_name: String,
|
||||
},
|
||||
JavaCheck,
|
||||
JavaLocate
|
||||
JavaLocate,
|
||||
MinecraftDownload,
|
||||
}
|
||||
|
||||
impl Drop for IngressId {
|
||||
|
|
|
|||
|
|
@ -43,13 +43,13 @@ impl Settings {
|
|||
}
|
||||
|
||||
async fn read() -> LauncherResult<Self> {
|
||||
let path = Dirs::get().await?.settings_file();
|
||||
let path = Dirs::get_settings_file().await?;
|
||||
let data = io::read(path).await?;
|
||||
Ok(serde_json::from_slice(&data)?)
|
||||
}
|
||||
|
||||
pub async fn save(&self) -> LauncherResult<()> {
|
||||
let path = Dirs::get().await?.settings_file();
|
||||
let path = Dirs::get_settings_file().await?;
|
||||
let data = serde_json::to_string_pretty(self)?;
|
||||
io::write(path, data).await?;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
#![allow(clippy::implicit_hasher)]
|
||||
|
||||
use std::{collections::HashMap, path::Path, sync::Arc};
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use bytes::Bytes;
|
||||
use reqwest::Method;
|
||||
|
|
@ -8,9 +10,13 @@ use serde::Deserialize;
|
|||
use tokio::sync::Semaphore;
|
||||
use tokio_stream::StreamExt;
|
||||
|
||||
use crate::{api::ingress::send_ingress, error::LauncherResult, store::{ingress::IngressId, Core, State}};
|
||||
use crate::api::ingress::send_ingress;
|
||||
use crate::error::LauncherResult;
|
||||
use crate::store::ingress::IngressRef;
|
||||
use crate::store::{Core, State};
|
||||
|
||||
use super::{crypto::{CryptoError, HashAlgorithm}, io};
|
||||
use super::crypto::{CryptoError, HashAlgorithm};
|
||||
use super::io;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FetchSemaphore(pub Arc<Semaphore>);
|
||||
|
|
@ -41,14 +47,14 @@ pub fn create_client() -> LauncherResult<reqwest::Client> {
|
|||
.build()?)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(body, headers, ingress))]
|
||||
#[tracing::instrument(skip(body, headers, ingress_ref))]
|
||||
pub async fn fetch_advanced(
|
||||
method: Method,
|
||||
url: &str,
|
||||
body: Option<serde_json::Value>,
|
||||
hash: Option<(HashAlgorithm, &str)>,
|
||||
headers: Option<HashMap<&str, &str>>,
|
||||
ingress: Option<(&IngressId, f64)>,
|
||||
ingress_ref: Option<&IngressRef<'_>>,
|
||||
) -> LauncherResult<Bytes> {
|
||||
let state = State::get().await?;
|
||||
let _permit = state.fetch_semaphore.0.acquire().await?;
|
||||
|
|
@ -70,22 +76,23 @@ pub async fn fetch_advanced(
|
|||
let res = req.send().await;
|
||||
|
||||
match res {
|
||||
Err(_) if attempt <= 3 => {},
|
||||
Err(_) if attempt <= 3 => {}
|
||||
Err(err) => return Err(err.into()),
|
||||
Ok(res) => {
|
||||
let bytes = if let Some((ingress_id, total)) = ingress {
|
||||
let bytes = if let Some(ingress_ref) = ingress_ref {
|
||||
let length = res.content_length();
|
||||
if let Some(total_size) = length {
|
||||
let mut stream = res.bytes_stream();
|
||||
let mut bytes = Vec::new();
|
||||
|
||||
while let Some(item) = stream.next().await {
|
||||
let chunk = item.or(Err(anyhow::anyhow!("no value for fetch bytes")))?;
|
||||
let chunk =
|
||||
item.or(Err(anyhow::anyhow!("no value for fetch bytes")))?;
|
||||
bytes.append(&mut chunk.to_vec());
|
||||
|
||||
send_ingress(
|
||||
ingress_id,
|
||||
(chunk.len() as f64 / total_size as f64) * total,
|
||||
ingress_ref,
|
||||
(chunk.len() as f64 / total_size as f64) * ingress_ref.increment_by,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
|
@ -111,8 +118,9 @@ pub async fn fetch_advanced(
|
|||
return Err(CryptoError::InvalidHash {
|
||||
algorithm: algorithm.clone(),
|
||||
expected: expected_hash,
|
||||
actual: calculated_hash
|
||||
}.into());
|
||||
actual: calculated_hash,
|
||||
}
|
||||
.into());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -121,7 +129,7 @@ pub async fn fetch_advanced(
|
|||
} else if let Err(err) = bytes {
|
||||
return Err(err.into());
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -147,7 +155,7 @@ pub async fn fetch_json_advanced<T: for<'de> Deserialize<'de>>(
|
|||
body: Option<serde_json::Value>,
|
||||
hash: Option<(HashAlgorithm, &str)>,
|
||||
headers: Option<HashMap<&str, &str>>,
|
||||
ingress: Option<(&IngressId, f64)>,
|
||||
ingress: Option<&IngressRef<'_>>,
|
||||
) -> LauncherResult<T> {
|
||||
let bytes = fetch_advanced(method, url, body, hash, headers, ingress).await?;
|
||||
Ok(serde_json::from_slice(&bytes)?)
|
||||
|
|
@ -160,13 +168,21 @@ pub async fn download_advanced(
|
|||
body: Option<serde_json::Value>,
|
||||
hash: Option<(HashAlgorithm, &str)>,
|
||||
headers: Option<HashMap<&str, &str>>,
|
||||
ingress: Option<(&IngressId, f64)>,
|
||||
ingress: Option<&IngressRef<'_>>,
|
||||
) -> LauncherResult<()> {
|
||||
let bytes = fetch_advanced(method, url, body, hash, headers, ingress.map(|(id, total)| (id, total / 2.0))).await?;
|
||||
let bytes = fetch_advanced(
|
||||
method,
|
||||
url,
|
||||
body,
|
||||
hash,
|
||||
headers,
|
||||
ingress.map(|i| i.with_increment(i.increment_by / 2.0)).as_ref(),
|
||||
)
|
||||
.await?;
|
||||
let path = path.as_ref();
|
||||
|
||||
if let Some((id, total)) = ingress {
|
||||
send_ingress(id, total / 4.0).await?;
|
||||
if let Some(ingress_ref) = ingress {
|
||||
send_ingress(ingress_ref, ingress_ref.increment_by / 4.0).await?;
|
||||
}
|
||||
|
||||
if let Some(parent) = path.parent() {
|
||||
|
|
@ -175,8 +191,8 @@ pub async fn download_advanced(
|
|||
|
||||
io::write(path, &bytes).await?;
|
||||
|
||||
if let Some((id, total)) = ingress {
|
||||
send_ingress(id, total / 4.0).await?;
|
||||
if let Some(ingress_ref) = ingress {
|
||||
send_ingress(ingress_ref, ingress_ref.increment_by / 4.0).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -186,7 +202,7 @@ pub async fn download(
|
|||
method: Method,
|
||||
url: &str,
|
||||
path: impl AsRef<Path>,
|
||||
hash: Option<(HashAlgorithm, &str)>
|
||||
hash: Option<(HashAlgorithm, &str)>,
|
||||
) -> LauncherResult<()> {
|
||||
download_advanced(method, url, path, None, hash, None, None).await
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue