feat: Lots of small tweaks and stuff, begin work on minecraft / interpulse metadata

This commit is contained in:
LynithDev 2025-04-04 19:48:23 +02:00
parent 2f7c26229a
commit 54d16e5d69
19 changed files with 375 additions and 238 deletions

1
Cargo.lock generated
View file

@ -2954,6 +2954,7 @@ dependencies = [
"onelauncher_gamemode",
"onelauncher_macro",
"onelauncher_migration",
"paste",
"rand 0.8.5",
"regex",
"reqwest",

View file

@ -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 }

View file

@ -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 }

View file

@ -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]

View file

@ -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<()> {

View file

@ -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<()> {

View file

@ -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())
}

View 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())
}

View file

@ -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;

View file

@ -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| {

View file

@ -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 {

View 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)
}

View file

@ -0,0 +1 @@
pub mod metadata;

View file

@ -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;

View file

@ -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
}

View file

@ -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")
}

View file

@ -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 {

View file

@ -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?;

View file

@ -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
}