In-app store, delta version support (#179)

* fix: windows launch

* feat: add necessary client fixes for store

* fix: keyring fix

* feat: delta version support

* feat: dl/disk progress

* feat: move to jwt auth

* fix: lint
This commit is contained in:
DecDuck
2026-02-06 00:30:27 +11:00
committed by GitHub
parent fc69ae30ab
commit 1f74d35bdc
23 changed files with 808 additions and 284 deletions
@@ -5,6 +5,7 @@ use std::{
};
use futures_util::StreamExt;
use log::warn;
use remote::{
error::RemoteAccessError,
requests::{generate_url, make_authenticated_get},
@@ -33,6 +34,7 @@ struct Depot {
manifest: Option<DepotManifest>,
latest_speed: Option<usize>, // bytes per second
current_downloads: SyncSemaphore,
enabled: bool
}
pub struct DepotManager {
@@ -53,6 +55,38 @@ impl DepotManager {
}
}
async fn sync_depot(&self, depot: &mut Depot) -> Result<(), RemoteAccessError> {
let manifest_url = Url::parse(&depot.endpoint)?.join("manifest.json")?;
let manifest = DROP_CLIENT_ASYNC.get(manifest_url).send().await?;
let manifest: DepotManifest = manifest.json().await?;
depot.manifest.replace(manifest);
let speedtest_url = Url::parse(&depot.endpoint)?.join("speedtest")?;
let speedtest = DROP_CLIENT_ASYNC.get(speedtest_url).send().await?;
let mut stream = speedtest.bytes_stream();
let start = Instant::now();
let mut total_length = 0;
while let Some(chunk) = stream.next().await {
let length = chunk?.len();
total_length += length;
if SPEEDTEST_TIMEOUT <= start.elapsed() {
break;
}
}
let elapsed = start.elapsed().as_millis() as usize;
let speed = if elapsed == 0 {
usize::MAX
} else {
(total_length / elapsed) * 1000
};
depot.latest_speed.replace(speed);
Ok(())
}
pub async fn sync_depots(&self) -> Result<(), RemoteAccessError> {
let depots = make_authenticated_get(generate_url(&["/api/v1/client/depots"], &[])?).await?;
let depots: Vec<ServersideDepot> = depots.json().await?;
@@ -68,33 +102,20 @@ impl DepotManager {
manifest: None,
latest_speed: None,
current_downloads: SyncSemaphore::new(),
enabled: true,
})
.collect::<Vec<Depot>>();
for depot in &mut new_depots {
let manifest_url = Url::parse(&depot.endpoint)?.join("manifest.json")?;
let manifest = DROP_CLIENT_ASYNC.get(manifest_url).send().await?;
let manifest: DepotManifest = manifest.json().await?;
depot.manifest.replace(manifest);
let speedtest_url = Url::parse(&depot.endpoint)?.join("speedtest")?;
let speedtest = DROP_CLIENT_ASYNC.get(speedtest_url).send().await?;
let mut stream = speedtest.bytes_stream();
let start = Instant::now();
let mut total_length = 0;
while let Some(chunk) = stream.next().await {
let length = chunk?.len();
total_length += length;
if SPEEDTEST_TIMEOUT <= start.elapsed() {
break;
}
if let Err(sync_error) = self.sync_depot(depot).await {
warn!("failed to sync depot {}: {:?}", depot.endpoint, sync_error);
depot.enabled = false;
}
let elapsed = start.elapsed().as_millis() as usize;
let speed = if elapsed == 0 { usize::MAX } else { (total_length / elapsed) * 1000 };
depot.latest_speed.replace(speed);
}
let enabled = new_depots.iter().filter(|v| v.enabled).count();
if enabled == 0 {
return Err(RemoteAccessError::NoDepots);
}
let mut depot_lock = self.depots.write().unwrap();
@@ -16,7 +16,9 @@ use crate::{
depot_manager::DepotManager,
download_manager_frontend::DownloadStatus,
error::ApplicationDownloadError,
frontend_updates::{QueueUpdateEvent, QueueUpdateEventQueueData, StatsUpdateEvent},
frontend_updates::{
DiskStatsUpdateEvent, DownloadStatsUpdateEvent, QueueUpdateEvent, QueueUpdateEventQueueData,
},
};
use super::{
@@ -107,7 +109,13 @@ impl DownloadManagerBuilder {
info!("download manager exited with result: {:?}", result);
});
DownloadManager::new(terminator, queue, active_progress, command_sender, depot_manager)
DownloadManager::new(
terminator,
queue,
active_progress,
command_sender,
depot_manager,
)
}
fn set_status(&self, status: DownloadManagerStatus) {
@@ -187,8 +195,8 @@ impl DownloadManagerBuilder {
DownloadManagerSignal::UpdateUIQueue => {
self.push_ui_queue_update();
}
DownloadManagerSignal::UpdateUIStats(kbs, time) => {
self.push_ui_stats_update(kbs, time);
DownloadManagerSignal::UpdateUIDownloadStats(kbs, time) => {
self.push_ui_download_stats_update(kbs, time);
}
DownloadManagerSignal::Finish => {
self.stop_and_wait_current_download().await;
@@ -266,17 +274,16 @@ impl DownloadManagerBuilder {
*download_thread_lock = Some(tauri::async_runtime::spawn(async move {
loop {
let download_result =
match download_agent.download(&app_handle).await {
// Ok(true) is for completed and exited properly
Ok(v) => v,
Err(e) => {
error!("download {:?} has error {}", download_agent.metadata(), &e);
download_agent.on_error(&app_handle, &e);
send!(sender, DownloadManagerSignal::Error(e));
return;
}
};
let download_result = match download_agent.download(&app_handle).await {
// Ok(true) is for completed and exited properly
Ok(v) => v,
Err(e) => {
error!("download {:?} has error {}", download_agent.metadata(), &e);
download_agent.on_error(&app_handle, &e);
send!(sender, DownloadManagerSignal::Error(e));
return;
}
};
// If the download gets canceled
// immediately return, on_cancelled gets called for us earlier
@@ -339,7 +346,7 @@ impl DownloadManagerBuilder {
send!(self.sender, DownloadManagerSignal::Go);
}
async fn manage_error_signal(&mut self, error: ApplicationDownloadError) {
info!("got signal Error");
warn!("got signal Error");
if let Some(metadata) = self.download_queue.read().front()
&& let Some(current_agent) = self.download_agent_registry.get(metadata)
{
@@ -384,9 +391,8 @@ impl DownloadManagerBuilder {
self.push_ui_queue_update();
send!(self.sender, DownloadManagerSignal::Go);
}
fn push_ui_stats_update(&self, kbs: usize, time: usize) {
let event_data = StatsUpdateEvent { speed: kbs, time };
fn push_ui_download_stats_update(&self, kbs: usize, time: usize) {
let event_data = DownloadStatsUpdateEvent { speed: kbs, time };
app_emit!(&self.app_handle, "update_stats", event_data);
}
fn push_ui_queue_update(&self) {
@@ -398,9 +404,12 @@ impl DownloadManagerBuilder {
QueueUpdateEventQueueData {
meta: DownloadableMetadata::clone(key),
status: val.status(),
progress: val.progress().get_progress(),
current: val.progress().sum(),
max: val.progress().get_max(),
dl_progress: val.dl_progress().get_progress(),
dl_current: val.dl_progress().sum(),
dl_max: val.dl_progress().get_max(),
disk_progress: val.disk_progress().get_progress(),
disk_current: val.disk_progress().sum(),
disk_max: val.disk_progress().get_max(),
}
})
.collect();
@@ -40,7 +40,7 @@ pub enum DownloadManagerSignal {
Error(ApplicationDownloadError),
/// Pushes UI update
UpdateUIQueue,
UpdateUIStats(usize, usize), //kb/s and seconds
UpdateUIDownloadStats(usize, usize), //kb/s and seconds
}
#[derive(Debug)]
@@ -106,7 +106,7 @@ impl DownloadManager {
download_queue,
progress,
command_sender,
depot_manager
depot_manager,
}
}
@@ -22,7 +22,8 @@ pub trait Downloadable: Send + Sync + Debug {
async fn download(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError>;
fn validate(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError>;
fn progress(&self) -> Arc<ProgressObject>;
fn dl_progress(&self) -> &Arc<ProgressObject>;
fn disk_progress(&self) -> &Arc<ProgressObject>;
fn control_flag(&self) -> DownloadThreadControl;
fn status(&self) -> DownloadStatus;
fn metadata(&self) -> DownloadableMetadata;
@@ -7,9 +7,12 @@ use crate::download_manager_frontend::DownloadStatus;
pub struct QueueUpdateEventQueueData {
pub meta: DownloadableMetadata,
pub status: DownloadStatus,
pub progress: f64,
pub current: usize,
pub max: usize,
pub dl_progress: f64,
pub dl_current: usize,
pub dl_max: usize,
pub disk_progress: f64,
pub disk_current: usize,
pub disk_max: usize,
}
#[derive(Serialize, Clone)]
@@ -18,7 +21,12 @@ pub struct QueueUpdateEvent {
}
#[derive(Serialize, Clone)]
pub struct StatsUpdateEvent {
pub struct DownloadStatsUpdateEvent {
pub speed: usize,
pub time: usize,
}
#[derive(Serialize, Clone)]
pub struct DiskStatsUpdateEvent {
pub speed: usize,
}
@@ -7,15 +7,22 @@ use std::{
};
use atomic_instant_full::AtomicInstant;
use utils::{lock, send};
use tokio::sync::mpsc::Sender;
use utils::{lock, send};
use crate::download_manager_frontend::DownloadManagerSignal;
use crate::{download_manager_frontend::DownloadManagerSignal, util::progress_object};
use super::rolling_progress_updates::RollingProgressWindow;
#[derive(Clone, Debug)]
pub enum ProgressType {
Download,
Disk,
}
#[derive(Clone, Debug)]
pub struct ProgressObject {
progress_type: ProgressType,
max: Arc<Mutex<usize>>,
progress_instances: Arc<Mutex<Vec<Arc<AtomicUsize>>>>,
start: Arc<Mutex<Instant>>,
@@ -45,7 +52,7 @@ impl ProgressHandle {
pub fn add(&self, amount: usize) {
self.progress
.fetch_add(amount, std::sync::atomic::Ordering::AcqRel);
tauri::async_runtime::spawn(calculate_update(self.progress_object.clone()));
spawn_update(&self.progress_object);
}
pub fn skip(&self, amount: usize) {
self.progress
@@ -59,7 +66,12 @@ impl ProgressHandle {
}
impl ProgressObject {
pub fn new(max: usize, length: usize, sender: Sender<DownloadManagerSignal>) -> Self {
pub fn new(
max: usize,
length: usize,
sender: Sender<DownloadManagerSignal>,
progress_type: ProgressType,
) -> Self {
let arr = Mutex::new((0..length).map(|_| Arc::new(AtomicUsize::new(0))).collect());
Self {
max: Arc::new(Mutex::new(max)),
@@ -70,6 +82,7 @@ impl ProgressObject {
last_update_time: Arc::new(AtomicInstant::now()),
bytes_last_update: Arc::new(AtomicUsize::new(0)),
rolling: RollingProgressWindow::new(),
progress_type,
}
}
@@ -111,17 +124,21 @@ impl ProgressObject {
}
}
pub async fn calculate_update(progress: Arc<ProgressObject>) {
let last_update_time = progress
.last_update_time
.load(Ordering::SeqCst);
pub fn spawn_update(progress: &Arc<ProgressObject>) {
let last_update_time = progress.last_update_time.load(Ordering::SeqCst);
let time_since_last_update = Instant::now()
.duration_since(last_update_time)
.as_millis_f64();
if time_since_last_update < 250.0 {
return;
}
progress.last_update_time.swap(Instant::now(), Ordering::SeqCst);
tauri::async_runtime::spawn(calculate_update(progress.clone(), time_since_last_update));
}
pub async fn calculate_update(progress: Arc<ProgressObject>, time_since_last_update: f64) {
progress
.last_update_time
.swap(Instant::now(), Ordering::SeqCst);
let current_bytes_downloaded = progress.sum();
let max = progress.get_max();
@@ -148,11 +165,18 @@ pub async fn push_update(progress: &ProgressObject, bytes_remaining: usize) {
update_queue(progress).await;
}
async fn update_ui(progress_object: &ProgressObject, kilobytes_per_second: usize, time_remaining: usize) {
send!(
progress_object.sender,
DownloadManagerSignal::UpdateUIStats(kilobytes_per_second, time_remaining)
);
async fn update_ui(
progress_object: &ProgressObject,
kilobytes_per_second: usize,
time_remaining: usize,
) {
match progress_object.progress_type {
ProgressType::Download => send!(
progress_object.sender,
DownloadManagerSignal::UpdateUIDownloadStats(kilobytes_per_second, time_remaining)
),
ProgressType::Disk => (),
}
}
async fn update_queue(progress: &ProgressObject) {