From e9550e8eee70f41467b93dc6fb2fe3725daac4ef Mon Sep 17 00:00:00 2001 From: akanyan Date: Thu, 17 Apr 2025 07:44:05 +0000 Subject: [PATCH] feat: global progress bar Also fix me having no foresight and executing things inside log::debug! macros --- TODO.md | 1 - rust/src/download_handler.rs | 10 ++-- rust/src/lib.rs | 16 ++++-- rust/src/model/rainy.rs | 1 + rust/src/pkg.rs | 4 +- rust/src/pkg_store.rs | 2 +- src/components/App.vue | 83 +++++++++++++++++++++++++++ src/components/InstallButton.vue | 13 ++++- src/components/ModList.vue | 6 +- src/components/UpdateButton.vue | 6 +- src/components/options/Startliner.vue | 2 +- src/stores.ts | 14 +++-- src/types.ts | 2 +- 13 files changed, 127 insertions(+), 33 deletions(-) diff --git a/TODO.md b/TODO.md index 81e3092..c7cd2dc 100644 --- a/TODO.md +++ b/TODO.md @@ -4,6 +4,5 @@ ### Long-term -- Progress bars and other GUI sugar - artemis as a special package - Other arcade games (if there is demand) diff --git a/rust/src/download_handler.rs b/rust/src/download_handler.rs index d404899..2c1b7a2 100644 --- a/rust/src/download_handler.rs +++ b/rust/src/download_handler.rs @@ -1,5 +1,4 @@ use std::{collections::HashSet, path::PathBuf}; -use futures::Stream; use serde::{Deserialize, Serialize}; use tauri::{AppHandle, Emitter}; use tokio::fs::File; @@ -15,7 +14,7 @@ pub struct DownloadHandler { #[derive(Serialize, Deserialize, Clone)] pub struct DownloadTick { pkg_key: PkgKey, - ratio: f32 + ratio: f32, } impl DownloadHandler { @@ -50,14 +49,15 @@ impl DownloadHandler { let mut cache_file_w = File::create(&zip_path_part).await?; let mut byte_stream = reqwest::get(&rmt.download_url).await?.bytes_stream(); - let first_hint = byte_stream.size_hint().0 as f32; + let mut total_bytes = 0; log::info!("downloading: {}", rmt.download_url); while let Some(item) = byte_stream.next().await { let i = item?; - app.emit("download-tick", DownloadTick { + total_bytes += i.len(); + _ = app.emit("download-progress", DownloadTick { pkg_key: pkg_key.clone(), - ratio: 1.0f32 - (byte_stream.size_hint().0 as f32) / first_hint + ratio: (total_bytes as f32) / (rmt.file_size as f32), })?; cache_file_w.write_all(&mut i.as_ref()).await?; } diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 627e6ec..77a69bb 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -102,14 +102,15 @@ pub async fn run(_args: Vec) { }); app.listen("download-end", closure!(clone apph, |ev| { - log::debug!("download-end triggered: {}", ev.payload()); let raw = ev.payload(); + log::debug!("download-end triggered: {}", raw); let key = PkgKey(raw[1..raw.len()-1].to_owned()); let apph = apph.clone(); tauri::async_runtime::spawn(async move { let mutex = apph.state::>(); let mut appd = mutex.lock().await; - log::debug!("download-end install {:?}", appd.pkgs.install_package(&key, true, false).await); + let res = appd.pkgs.install_package(&key, true, false).await; + log::debug!("download-end install {:?}", res); }); })); @@ -126,19 +127,21 @@ pub async fn run(_args: Vec) { })); app.listen("install-end-prelude", closure!(clone apph, |ev| { - log::debug!("install-end-prelude triggered: {}", ev.payload()); let payload = serde_json::from_str::(ev.payload()); + log::debug!("install-end-prelude triggered: {:?}", payload); let apph = apph.clone(); if let Ok(payload) = payload { tauri::async_runtime::spawn(async move { let mutex = apph.state::>(); let mut appd = mutex.lock().await; + let res = appd.toggle_package(payload.pkg.clone(), ToggleAction::EnableSelf); log::debug!( "install-end-prelude toggle {:?}", - appd.toggle_package(payload.pkg.clone(), ToggleAction::EnableSelf) + res ); use tauri::Emitter; - log::debug!("install-end {:?}", apph.emit("install-end", payload)); + let res = apph.emit("install-end", payload); + log::debug!("install-end {:?}", res); }); } else { log::error!("install-end-prelude: invalid payload: {}", ev.payload()); @@ -232,7 +235,8 @@ pub async fn run(_args: Vec) { let mutex = app.state::>(); let appd = mutex.lock().await; if let Some(p) = &appd.profile { - log::debug!("save: {:?}", p.save()); + let res = p.save(); + log::debug!("save: {:?}", res); app.exit(0); } }); diff --git a/rust/src/model/rainy.rs b/rust/src/model/rainy.rs index 85d49f2..34139f5 100644 --- a/rust/src/model/rainy.rs +++ b/rust/src/model/rainy.rs @@ -22,4 +22,5 @@ pub struct V1Version { pub icon: String, pub dependencies: BTreeSet, pub download_url: String, + pub file_size: i64, } \ No newline at end of file diff --git a/rust/src/pkg.rs b/rust/src/pkg.rs index b99a823..020023f 100644 --- a/rust/src/pkg.rs +++ b/rust/src/pkg.rs @@ -81,6 +81,7 @@ pub struct Remote { pub nsfw: bool, pub categories: Vec, pub dependencies: BTreeSet, + pub file_size: i64, } impl PkgKey { @@ -112,7 +113,8 @@ impl Package { nsfw: p.has_nsfw_content, version: v.version_number, categories: p.categories, - dependencies: Self::sanitize_deps(v.dependencies) + dependencies: Self::sanitize_deps(v.dependencies), + file_size: v.file_size }), source: PackageSource::Rainy, }) diff --git a/rust/src/pkg_store.rs b/rust/src/pkg_store.rs index 327dc57..e7f7fee 100644 --- a/rust/src/pkg_store.rs +++ b/rust/src/pkg_store.rs @@ -21,7 +21,7 @@ pub struct PackageStore { offline: bool, } -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize, Debug)] pub struct Payload { pub pkg: PkgKey } diff --git a/src/components/App.vue b/src/components/App.vue index 927cda4..98ff092 100644 --- a/src/components/App.vue +++ b/src/components/App.vue @@ -88,6 +88,60 @@ listen('launch-error', (event) => { errorMessage.value = event.payload; errorHeader.value = 'Launch error'; }); + +interface DownloadingStatus { + ratio: number; + pkg_key: string; +} +const downloading_status: Ref = ref([]); + +const download_value = computed(() => { + return ( + downloading_status.value.map((v) => v.ratio).reduce((a, v) => a * v) * + 100 + ); +}); + +const downloadProgressText = computed(() => { + if (download_value.value < 7) { + return ''; + } + let pkgs = `${downloading_status.value.length} package${downloading_status.value.length === 1 ? '' : 's'}`; + if (download_value.value < 14) { + return pkgs; + } else { + return `${pkgs} (${Math.floor(download_value.value)}%)`; + } +}); + +listen('download-progress', (event) => { + let status = downloading_status.value.find( + (v) => v.pkg_key === event.payload.pkg_key + ); + + if (status === undefined) { + status = { + ratio: 0, + pkg_key: event.payload.pkg_key, + }; + downloading_status.value.push(status); + } + status.ratio = event.payload.ratio; + + const remove = () => { + if (status !== undefined) { + downloading_status.value = downloading_status.value.filter( + (v) => v.pkg_key !== event.payload.pkg_key + ); + } + }; + + if (status.ratio === 1.0) { + remove(); + } + + setTimeout(() => remove, 10_000); +});