From 3abb479fbf2345e59cda4acbbfc371872349f119 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 26 Aug 2025 21:26:39 +0530 Subject: [PATCH] feat(rust): Add progress indicators for downloads Co-Authored-By: Claude --- rust/src/commands/sync.rs | 2 +- rust/src/sync/download.rs | 53 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/rust/src/commands/sync.rs b/rust/src/commands/sync.rs index e8a709707e..65cb2dc29c 100644 --- a/rust/src/commands/sync.rs +++ b/rust/src/commands/sync.rs @@ -173,7 +173,7 @@ async fn sync_account( if !to_download.is_empty() { println!("📥 Downloading {} new files", to_download.len()); - // Download files + // Download files with progress bar let download_stats = download_manager .download_files(&account.email, to_download) .await?; diff --git a/rust/src/sync/download.rs b/rust/src/sync/download.rs index 09658a5a03..318e380f80 100644 --- a/rust/src/sync/download.rs +++ b/rust/src/sync/download.rs @@ -4,8 +4,10 @@ use crate::api::methods::ApiMethods; use crate::crypto::{decrypt_file_data, secret_box_open}; use crate::models::file::RemoteFile; use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; +use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use std::collections::HashMap; use std::path::{Path, PathBuf}; +use std::sync::Arc; use tokio::fs; use tokio::io::AsyncWriteExt; @@ -15,6 +17,7 @@ pub struct DownloadManager { temp_dir: PathBuf, pub collection_keys: HashMap>, concurrent_downloads: usize, + show_progress: bool, } impl DownloadManager { @@ -28,6 +31,7 @@ impl DownloadManager { temp_dir, collection_keys: HashMap::new(), concurrent_downloads: 4, // Default concurrent downloads + show_progress: true, }) } @@ -41,12 +45,29 @@ impl DownloadManager { self.concurrent_downloads = count.clamp(1, 10); // Limit between 1-10 } + /// Set whether to show progress indicators + pub fn set_show_progress(&mut self, show: bool) { + self.show_progress = show; + } + /// Download a single file pub async fn download_file( &self, account_id: &str, file: &RemoteFile, destination: &Path, + ) -> Result<()> { + self.download_file_with_progress(account_id, file, destination, None) + .await + } + + /// Download a single file with optional progress bar + async fn download_file_with_progress( + &self, + account_id: &str, + file: &RemoteFile, + destination: &Path, + progress: Option>, ) -> Result<()> { log::debug!("Downloading file {} to {:?}", file.id, destination); @@ -88,6 +109,11 @@ impl DownloadManager { // TODO: Update storage with local path // self.storage.sync().update_file_local_path(file.id, destination.to_str().unwrap())?; + // Update progress if available + if let Some(pb) = progress { + pb.inc(1); + } + log::info!("Downloaded file {} to {:?}", file.id, destination); Ok(()) } @@ -108,14 +134,34 @@ impl DownloadManager { ..Default::default() }; + // Create progress bars if enabled + let (_multi_progress, progress_bar) = if self.show_progress && total > 0 { + let mp = MultiProgress::new(); + let pb = mp.add(ProgressBar::new(total as u64)); + pb.set_style( + ProgressStyle::default_bar() + .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({percent}%) {msg}") + .unwrap() + .progress_chars("#>-"), + ); + pb.set_message("Downloading files..."); + (Some(Arc::new(mp)), Some(Arc::new(pb))) + } else { + (None, None) + }; + // Process files in parallel with concurrency limit + let pb_clone = progress_bar.clone(); let results: Vec<_> = stream::iter(files) .map(|(file, path)| { let account_id = account_id.to_string(); let file_clone = file.clone(); let path_clone = path.clone(); + let pb = pb_clone.clone(); async move { - let result = self.download_file(&account_id, &file, &path).await; + let result = self + .download_file_with_progress(&account_id, &file, &path, pb) + .await; (file_clone, path_clone, result) } }) @@ -137,6 +183,11 @@ impl DownloadManager { } } + // Finish progress bar + if let Some(pb) = progress_bar { + pb.finish_with_message(format!("Downloaded {} files", stats.successful)); + } + log::info!( "Download completed: {} successful, {} failed", stats.successful,