feat(ui): better downloading information

This commit is contained in:
LynithDev 2026-07-13 16:41:33 +02:00
parent 557b0e4b7b
commit 44897b3e7f
8 changed files with 103 additions and 20 deletions

View file

@ -804,16 +804,28 @@ async fn install_package(
.get_version(project_id, version_id, &state.services)
.await?;
PackageStore::install_to_cluster(
let session = oneclient_core::notification::GroupedProgressSession::start(
&state.services.notifier,
format!("Installing {}", project.name),
);
let size = version.primary_file().map(|f| f.size).unwrap_or(0);
let child = session.child(project.name.clone(), size);
let result = PackageStore::install_to_cluster(
provider,
&project,
&version,
cluster_id,
false,
false,
Some(&child),
&state.services,
)
.await?;
.await;
child.finish();
session.finish();
result?;
Ok(())
}

View file

@ -57,6 +57,7 @@ struct TaskLine {
struct GroupedAgg {
children: HashMap<Uuid, TaskLine>,
done_units: u64,
carried: u64,
}
impl GroupedAgg {
@ -82,7 +83,8 @@ impl GroupedAgg {
}
fn downloaded_bytes(&self) -> u64 {
self.done_units
self.carried
+ self.done_units
+ self
.children
.values()
@ -260,8 +262,12 @@ impl Component for OnboardingDownloading {
let is_running = *running.read();
let is_complete = *complete.read();
let global = if total == 0 {
if is_complete { 100.0 } else { 0.0 }
let global = if is_complete {
100.0
} else if total == 0 {
0.0
} else if total_estimate_now > 0 {
(agg_now.downloaded_bytes() as f32 / total_estimate_now as f32 * 100.0).clamp(0.0, 99.0)
} else {
((done as f32 + agg_now.fraction()) / total as f32 * 100.0).clamp(0.0, 100.0)
};
@ -531,7 +537,7 @@ fn run_install_batch(plans: Vec<ClusterPlan>, predownload: bool, handles: Instal
let downloaded = agg.peek().downloaded_bytes();
let delta = downloaded.saturating_sub(prev);
let inst = delta as f64 / 0.5;
let inst = delta as f64;
speed = if speed <= 0.0 {
inst
@ -755,6 +761,12 @@ fn apply_grouped(agg: &mut GroupedAgg, event: GroupedProgressEvent) {
}
}
GroupedProgressEvent::End { .. } => {
agg.carried += agg.done_units
+ agg
.children
.values()
.map(|t| t.current.min(t.total))
.sum::<u64>();
agg.children.clear();
agg.done_units = 0;
}

View file

@ -10,7 +10,7 @@ use crate::bundles::error::BundleError;
use crate::bundles::manager::BundlesManager;
use crate::bundles::overrides;
use crate::bundles::types::{BundleArchive, BundleFile, BundleFileKind};
use crate::notification::{GroupedProgressSession, TaskPhase};
use crate::notification::{GroupedProgressChild, GroupedProgressSession, TaskPhase};
use crate::packages::domain::{ContentType, GameLoader};
use crate::packages::error::PackageError;
use crate::packages::store::{PackageStore, unlink_cluster_file};
@ -26,6 +26,7 @@ pub async fn install_package_from_bundle(
cluster_id: i64,
bundle_name: &str,
skip_compatibility: bool,
child: Option<&GroupedProgressChild>,
services: &LauncherServices,
) -> LauncherResult<String> {
let cluster = PackageStore::get_cluster(cluster_id, services).await?;
@ -63,13 +64,14 @@ pub async fn install_package_from_bundle(
cluster_id,
skip_compatibility,
false,
child,
services,
)
.await?;
artifact.hash
}
BundleFileKind::External(ext) => {
install_external(ext, &cluster, skip_compatibility, services).await?
install_external(ext, &cluster, skip_compatibility, child, services).await?
}
};
@ -90,9 +92,10 @@ async fn install_external(
ext: &ExternalFile,
cluster: &ClusterRow,
skip_compatibility: bool,
child: Option<&GroupedProgressChild>,
services: &LauncherServices,
) -> LauncherResult<String> {
let artifact = crate::packages::store::download_external(ext, false, services).await?;
let artifact = crate::packages::store::download_external(ext, false, child, services).await?;
PackageStore::link_artifact(&artifact, cluster, Some(&ext.name), services).await?;
let _ = skip_compatibility;
@ -197,8 +200,8 @@ pub async fn install_enabled_bundle_files(
let bundle_name = &bundle_name;
let results = futures_util::stream::iter(to_install.into_iter().map(|file| async move {
let child = progress.map(|p| {
let c = p.child(format!("Mod {}", file.display_name()), 1);
c.set_phase(TaskPhase::Installing);
let c = p.child(format!("Mod {}", file.display_name()), file.size.max(1));
c.set_phase(TaskPhase::Downloading);
c
});
@ -207,11 +210,13 @@ pub async fn install_enabled_bundle_files(
cluster_id,
bundle_name,
skip_compatibility,
child.as_ref(),
services,
)
.await;
if let Some(child) = child {
child.set_phase(TaskPhase::Installing);
child.finish();
}
(file.display_name(), result)

View file

@ -328,8 +328,21 @@ pub async fn apply_bundle_updates(
}
}
let session = (!check.updates_available.is_empty() || !check.additions_available.is_empty())
.then(|| {
crate::notification::GroupedProgressSession::start(
&services.notifier,
"Updating bundle content",
)
});
for update in check.updates_available {
match apply_single_update(&update, &overrides, services).await {
let child = session.as_ref().map(|s| {
let c = s.child(update.new_file.display_name(), update.new_file.size.max(1));
c.set_phase(crate::notification::TaskPhase::Downloading);
c
});
match apply_single_update(&update, &overrides, child.as_ref(), services).await {
Ok(_) => result.updates_applied.push(update),
Err(err) => result.updates_failed.push(err.to_string()),
}
@ -337,12 +350,21 @@ pub async fn apply_bundle_updates(
for addition in check.additions_available {
let file_id = addition.new_file.kind.package_id();
match apply_single_addition(&addition, &overrides, services).await {
let child = session.as_ref().map(|s| {
let c = s.child(addition.new_file.display_name(), addition.new_file.size.max(1));
c.set_phase(crate::notification::TaskPhase::Downloading);
c
});
match apply_single_addition(&addition, &overrides, child.as_ref(), services).await {
Ok(_) => result.additions_applied.push(addition),
Err(err) => result.additions_failed.push(format!("{file_id}: {err:#}")),
}
}
if let Some(session) = session {
session.finish();
}
let has_changes = !result.updates_applied.is_empty()
|| !result.removals_applied.is_empty()
|| !result.additions_applied.is_empty();
@ -389,6 +411,7 @@ pub async fn apply_bundle_updates(
async fn apply_single_update(
update: &BundlePackageUpdate,
overrides: &[ClusterBundleOverrideRow],
child: Option<&crate::notification::GroupedProgressChild>,
services: &LauncherServices,
) -> LauncherResult<()> {
let hash = install_package_from_bundle(
@ -396,9 +419,14 @@ async fn apply_single_update(
update.cluster_id,
&update.bundle_name,
true,
child,
services,
)
.await?;
if let Some(child) = child {
child.set_phase(crate::notification::TaskPhase::Installing);
child.finish();
}
let file_id = update.new_file.kind.package_id();
if should_be_disabled(&update.bundle_name, &file_id, overrides) {
@ -415,6 +443,7 @@ async fn apply_single_update(
async fn apply_single_addition(
addition: &BundlePackageAddition,
overrides: &[ClusterBundleOverrideRow],
child: Option<&crate::notification::GroupedProgressChild>,
services: &LauncherServices,
) -> LauncherResult<()> {
let hash = install_package_from_bundle(
@ -422,9 +451,14 @@ async fn apply_single_addition(
addition.cluster_id,
&addition.bundle_name,
true,
child,
services,
)
.await?;
if let Some(child) = child {
child.set_phase(crate::notification::TaskPhase::Installing);
child.finish();
}
let file_id = addition.new_file.kind.package_id();
if should_be_disabled(&addition.bundle_name, &file_id, overrides) {

View file

@ -168,6 +168,7 @@ async fn install_mrpack_file(
&project,
&version,
false,
None,
services,
)
.await?;
@ -189,7 +190,7 @@ async fn install_mrpack_file(
content_type,
};
let artifact = store::download_external(&external, false, services).await?;
let artifact = store::download_external(&external, false, None, services).await?;
PackageStore::link_artifact(&artifact, cluster, Some(&file_name), services).await?;
Ok(())

View file

@ -1,7 +1,8 @@
use reqwest::Method;
use serde::de::DeserializeOwned;
use crate::http::{RequestClient, RequestError, ResponseOptions};
use crate::http::{RequestClient, RequestError, ResponseNotifyOptions, ResponseOptions};
use crate::notification::GroupedProgressChild;
use crate::state::LauncherServices;
pub async fn fetch_json<T: DeserializeOwned>(
@ -33,10 +34,14 @@ pub async fn download_url(
client: &RequestClient,
url: &str,
dest: impl AsRef<std::path::Path> + Send,
child: Option<&GroupedProgressChild>,
services: &LauncherServices,
) -> Result<(), RequestError> {
let request = reqwest::Request::new(Method::GET, url.parse()?);
let options = ResponseOptions {
notify: child.map(|c| ResponseNotifyOptions::grouped(c.clone())),
};
client
.download_file(request, dest, ResponseOptions::default(), services)
.download_file(request, dest, options, services)
.await
}

View file

@ -6,6 +6,7 @@ use crate::LauncherResult;
use crate::crypto::{normalize_hash, sha1_file};
use crate::packages::domain::{ContentType, ProviderId};
use crate::packages::error::PackageError;
use crate::notification::{GroupedProgressChild, TaskPhase};
use crate::packages::provider::http::download_url;
use crate::packages::types::{ExternalFile, VersionDetail, VersionFile};
use crate::state::LauncherServices;
@ -14,6 +15,7 @@ pub async fn ensure_artifact_file(
hash: &str,
url: &str,
dest: &std::path::Path,
child: Option<&GroupedProgressChild>,
services: &LauncherServices,
) -> LauncherResult<u64> {
polyio::create_dir_all(dest.parent().unwrap_or(dest)).await?;
@ -26,8 +28,11 @@ pub async fn ensure_artifact_file(
tokio::fs::remove_file(dest).await?;
}
download_url(&services.requester, url, dest, services).await?;
download_url(&services.requester, url, dest, child, services).await?;
if let Some(child) = child {
child.set_phase(TaskPhase::Verifying);
}
let actual = sha1_file(dest).await?;
if normalize_hash(&actual) != normalize_hash(hash) {
return Err(PackageError::HashMismatch {
@ -40,6 +45,7 @@ pub async fn ensure_artifact_file(
Ok(tokio::fs::metadata(dest).await?.len())
}
#[allow(clippy::too_many_arguments)]
pub async fn download_version_file(
provider: ProviderId,
project_id: &str,
@ -47,6 +53,7 @@ pub async fn download_version_file(
content_type: ContentType,
file: &VersionFile,
force: bool,
child: Option<&GroupedProgressChild>,
services: &LauncherServices,
) -> LauncherResult<ArtifactRow> {
let hash = normalize_hash(&file.sha1);
@ -68,7 +75,7 @@ pub async fn download_version_file(
&file.file_name,
)?;
let size = ensure_artifact_file(&hash, &file.url, &dest, services).await?;
let size = ensure_artifact_file(&hash, &file.url, &dest, child, services).await?;
let stored_path = relative_cache_path(&dest)?;
let published_at = version.published.to_rfc3339();
@ -103,6 +110,7 @@ pub async fn download_version_file(
pub async fn download_external(
file: &ExternalFile,
force: bool,
child: Option<&GroupedProgressChild>,
services: &LauncherServices,
) -> LauncherResult<ArtifactRow> {
let hash = normalize_hash(&file.sha1);
@ -120,7 +128,7 @@ pub async fn download_external(
&hash[..hash.len().min(16)],
&file.name,
)?;
let size = ensure_artifact_file(&hash, &file.url, &dest, services).await?;
let size = ensure_artifact_file(&hash, &file.url, &dest, child, services).await?;
let stored_path = relative_cache_path(&dest)?;
artifact_dao::insert_artifact(

View file

@ -13,6 +13,7 @@ use super::domain::{ContentType, GameLoader, ProviderId};
use super::error::PackageError;
use super::types::{CachedArtifact, ProjectDetail, ProviderReleaseInfo, VersionDetail, LinkedArtifactInfo};
use crate::crypto::{normalize_hash, sha1_file};
use crate::notification::GroupedProgressChild;
use crate::state::LauncherServices;
use crate::{LauncherError, LauncherResult};
use std::path::Path;
@ -45,6 +46,7 @@ impl PackageStore {
project: &ProjectDetail,
version: &VersionDetail,
force: bool,
child: Option<&GroupedProgressChild>,
services: &LauncherServices,
) -> LauncherResult<ArtifactRow> {
let file = version.primary_file().ok_or(PackageError::NoPrimaryFile)?;
@ -56,11 +58,13 @@ impl PackageStore {
project.content_type,
file,
force,
child,
services,
)
.await
}
#[allow(clippy::too_many_arguments)]
pub async fn install_to_cluster(
provider_id: ProviderId,
project: &ProjectDetail,
@ -68,6 +72,7 @@ impl PackageStore {
cluster_id: i64,
skip_compatibility: bool,
force_download: bool,
child: Option<&GroupedProgressChild>,
services: &LauncherServices,
) -> LauncherResult<ArtifactRow> {
let cluster = Self::get_cluster(cluster_id, services).await?;
@ -81,6 +86,7 @@ impl PackageStore {
project,
version,
force_download,
child,
services,
)
.await?;
@ -286,7 +292,7 @@ impl PackageStore {
let project = provider.get_project(project_id, services).await?;
Self::download_and_cache(provider_id, &project, &version, false, services).await
Self::download_and_cache(provider_id, &project, &version, false, None, services).await
}
}