mirror of
https://github.com/Drop-OSS/drop.git
synced 2026-07-24 00:42:51 +10:00
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:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user