From 06e10b1f788ddd57d8384792af18973725c0a32c Mon Sep 17 00:00:00 2001 From: Yuhki Date: Thu, 27 Mar 2025 17:54:22 +0100 Subject: [PATCH] small cleanup in main.rs and updated config function to ask the user wether to download default or preload if both exist in index response --- src/io/util.rs | 122 ++++++++++++++++++++++++++++++++- src/main.rs | 153 ++++++++---------------------------------- src/network/client.rs | 55 +++++++++++---- 3 files changed, 190 insertions(+), 140 deletions(-) diff --git a/src/io/util.rs b/src/io/util.rs index 440b3d3..9d039ec 100644 --- a/src/io/util.rs +++ b/src/io/util.rs @@ -1,9 +1,10 @@ -use std::time::Duration; +use std::{fs::File, io, sync::Arc, thread, time::{Duration, Instant}}; use colored::Colorize; use reqwest::blocking::Client; use serde_json::Value; +use winconsole::console::{clear, set_title}; -use crate::config::{cfg::Config, status::Status}; +use crate::{config::{cfg::Config, status::Status}, download::progress::DownloadProgress, io::logging::log_error, network::client::download_file}; pub fn format_duration(duration: Duration) -> String { let secs = duration.as_secs(); @@ -90,4 +91,121 @@ pub fn get_version(data: &Value, category: &str, version: &str) -> Result ! { + log_error(log_file, error); + clear().unwrap(); + println!("{} {}", Status::error(), error); + println!("\n{} Press Enter to exit...", Status::warning()); + let _ = io::stdin().read_line(&mut String::new()); + std::process::exit(1); +} + +pub fn track_progress(total_size: u64) -> ( + Arc, + Arc, + DownloadProgress, +) { + let should_stop = Arc::new(std::sync::atomic::AtomicBool::new(false)); + let success = Arc::new(std::sync::atomic::AtomicUsize::new(0)); + + let progress = DownloadProgress { + total_bytes: Arc::new(std::sync::atomic::AtomicU64::new(total_size)), + downloaded_bytes: Arc::new(std::sync::atomic::AtomicU64::new(0)), + start_time: Instant::now(), + }; + + (should_stop, success, progress) +} + +pub fn start_title_thread( + should_stop: Arc, + success: Arc, + progress: DownloadProgress, + total_files: usize, +) -> thread::JoinHandle<()> { + thread::spawn(move || { + while !should_stop.load(std::sync::atomic::Ordering::SeqCst) { + let elapsed = progress.start_time.elapsed(); + let elapsed_secs = elapsed.as_secs(); + let downloaded_bytes = progress.downloaded_bytes.load(std::sync::atomic::Ordering::SeqCst); + let total_bytes = progress.total_bytes.load(std::sync::atomic::Ordering::SeqCst); + let current_success = success.load(std::sync::atomic::Ordering::SeqCst); + + let speed = if elapsed_secs > 0 { downloaded_bytes / elapsed_secs } else { 0 }; + let (speed_value, speed_unit) = if speed > 1_000_000 { + (speed / 1_000_000, "MB/s") + } else { + (speed / 1_000, "KB/s") + }; + + let remaining_files = total_files - current_success; + let remaining_bytes = total_bytes.saturating_sub(downloaded_bytes); + let eta_secs = if speed > 0 && remaining_files > 0 { remaining_bytes / speed } else { 0 }; + let eta_str = format_duration(Duration::from_secs(eta_secs)); + + let progress_percent = if total_bytes > 0 { + format!(" ({}%)", (downloaded_bytes * 100 / total_bytes)) + } else { + String::new() + }; + + let title = format!( + "Wuthering Waves Downloader - {}/{} files - Current File: {}{} - Speed: {}{} - Total ETA: {}", + current_success, + total_files, + bytes_to_human(downloaded_bytes), + progress_percent, + speed_value, + speed_unit, + eta_str + ); + + set_title(&title).unwrap(); + thread::sleep(Duration::from_secs(1)); + } + }) +} + +pub fn setup_ctrlc(should_stop: Arc) { + ctrlc::set_handler(move || { + should_stop.store(true, std::sync::atomic::Ordering::SeqCst); + clear().unwrap(); + println!("\n{} Download interrupted by user", Status::warning()); + }) + .unwrap(); +} + +pub fn download_resources( + client: &Client, + config: &Config, + resources: &[Value], + folder: &std::path::Path, + log_file: &File, + should_stop: &Arc, + progress: &DownloadProgress, + success: &Arc, +) { + for item in resources { + if should_stop.load(std::sync::atomic::Ordering::SeqCst) { + break; + } + + if let Some(dest) = item.get("dest").and_then(Value::as_str) { + let md5 = item.get("md5").and_then(Value::as_str); + if download_file( + client, + config, + dest, + folder, + md5, + log_file, + should_stop, + progress, + ) { + success.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + } + } + } } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 8f08602..a274e02 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,26 +1,19 @@ use colored::*; use reqwest::blocking::Client; use serde_json::Value; -use std::{ - io, - sync::Arc, - thread, - time::{Duration, Instant}, -}; use winconsole::console::{clear, set_title}; use wuwa_downloader::{ config::status::Status, - download::progress::DownloadProgress, io::{ console::print_results, file::get_dir, - util::{bytes_to_human, format_duration, calculate_total_size}, - logging::{ - log_error, setup_logging, + util::{ + calculate_total_size, download_resources, exit_with_error, setup_ctrlc, track_progress, start_title_thread }, + logging::setup_logging, }, - network::client::{download_file, fetch_index, get_predownload}, + network::client::{fetch_index, get_config}, }; fn main() { @@ -28,16 +21,9 @@ fn main() { let log_file = setup_logging(); let client = Client::new(); - let config = match get_predownload(&client) { + let config = match get_config(&client) { Ok(c) => c, - Err(e) => { - log_error(&log_file, &e); - clear().unwrap(); - println!("{} {}", Status::error(), e); - println!("\n{} Press Enter to exit...", Status::warning()); - let _ = io::stdin().read_line(&mut String::new()); - std::process::exit(1); - } + Err(e) => exit_with_error(&log_file, &e), }; let folder = get_dir(); @@ -48,18 +34,10 @@ fn main() { folder.display().to_string().cyan() ); - clear().unwrap(); let data = fetch_index(&client, &config, &log_file); - let resources = match data.get("resource").and_then(Value::as_array) { Some(res) => res, - None => { - log_error(&log_file, "No resources found in index file"); - println!("{} No resources found in index file", Status::warning()); - println!("\n{} Press Enter to exit...", Status::warning()); - let _ = io::stdin().read_line(&mut String::new()); - return; - } + None => exit_with_error(&log_file, "No resources found in index file"), }; println!( @@ -67,108 +45,31 @@ fn main() { Status::info(), resources.len().to_string().cyan() ); + let total_size = calculate_total_size(resources, &client, &config); clear().unwrap(); - let should_stop = Arc::new(std::sync::atomic::AtomicBool::new(false)); - let success = Arc::new(std::sync::atomic::AtomicUsize::new(0)); - let total_files = resources.len(); + let (should_stop, success, progress) = track_progress(total_size); - let progress = DownloadProgress { - total_bytes: Arc::new(std::sync::atomic::AtomicU64::new(total_size)), - downloaded_bytes: Arc::new(std::sync::atomic::AtomicU64::new(0)), - start_time: Instant::now(), - }; + let title_thread = start_title_thread( + should_stop.clone(), + success.clone(), + progress.clone(), + resources.len(), + ); - let success_clone = success.clone(); - let should_stop_clone = should_stop.clone(); - let progress_clone = progress.clone(); - let title_thread = thread::spawn(move || { - while !should_stop_clone.load(std::sync::atomic::Ordering::SeqCst) { - let elapsed = progress_clone.start_time.elapsed(); - let elapsed_secs = elapsed.as_secs(); - let downloaded_bytes = progress_clone - .downloaded_bytes - .load(std::sync::atomic::Ordering::SeqCst); - let total_bytes = progress_clone - .total_bytes - .load(std::sync::atomic::Ordering::SeqCst); - let current_success = success_clone.load(std::sync::atomic::Ordering::SeqCst); + setup_ctrlc(should_stop.clone()); - let speed = if elapsed_secs > 0 { - downloaded_bytes / elapsed_secs - } else { - 0 - }; - - let (speed_value, speed_unit) = if speed > 1_000_000 { - (speed / 1_000_000, "MB/s") - } else { - (speed / 1_000, "KB/s") - }; - - let remaining_files = total_files - current_success; - let remaining_bytes = total_bytes.saturating_sub(downloaded_bytes); - - let eta_secs = if speed > 0 && remaining_files > 0 { - remaining_bytes / speed - } else { - 0 - }; - let eta_str = format_duration(Duration::from_secs(eta_secs)); - - let progress_percent = if total_bytes > 0 { - format!(" ({}%)", (downloaded_bytes * 100 / total_bytes)) - } else { - String::new() - }; - - let title = format!( - "Wuthering Waves Downloader - {}/{} files - Current File: {}{} - Speed: {}{} - Total ETA: {}", - current_success, - total_files, - bytes_to_human(downloaded_bytes), - progress_percent, - speed_value, - speed_unit, - eta_str - ); - - set_title(&title).unwrap(); - thread::sleep(Duration::from_secs(1)); - } - }); - - let should_stop_ctrlc = should_stop.clone(); - - ctrlc::set_handler(move || { - should_stop_ctrlc.store(true, std::sync::atomic::Ordering::SeqCst); - clear().unwrap(); - println!("\n{} Download interrupted by user", Status::warning()); - }) - .unwrap(); - - for item in resources.iter() { - if should_stop.load(std::sync::atomic::Ordering::SeqCst) { - break; - } - - if let Some(dest) = item.get("dest").and_then(Value::as_str) { - let md5 = item.get("md5").and_then(Value::as_str); - if download_file( - &client, - &config, - dest, - &folder, - md5, - &log_file, - &should_stop, - &progress, - ) { - success.fetch_add(1, std::sync::atomic::Ordering::SeqCst); - } - } - } + download_resources( + &client, + &config, + resources, + &folder, + &log_file, + &should_stop, + &progress, + &success, + ); should_stop.store(true, std::sync::atomic::Ordering::SeqCst); title_thread.join().unwrap(); @@ -176,7 +77,7 @@ fn main() { clear().unwrap(); print_results( success.load(std::sync::atomic::Ordering::SeqCst), - total_files, + resources.len(), &folder, ); } diff --git a/src/network/client.rs b/src/network/client.rs index 1135d59..bbc4dce 100644 --- a/src/network/client.rs +++ b/src/network/client.rs @@ -291,7 +291,7 @@ fn download_single_file( Ok(()) } -pub fn get_predownload(client: &Client) -> Result { +pub fn get_config(client: &Client) -> Result { let selected_index_url = fetch_gist(client)?; println!("{} Fetching download configuration...", Status::info()); @@ -327,26 +327,57 @@ pub fn get_predownload(client: &Client) -> Result { from_reader(response).map_err(|e| format!("Invalid JSON: {}", e))? }; - let predownload_config = config - .get("predownload") - .and_then(|p| p.get("config")) - .ok_or("Missing predownload.config in response")?; + let has_default = config.get("default").is_some(); + let has_predownload = config.get("predownload").is_some(); - let base_url = predownload_config + let selected_config = match (has_default, has_predownload) { + (true, false) => { + println!("{} Using default.config", Status::info()); + "default" + } + (false, true) => { + println!("{} Using predownload.config", Status::info()); + "predownload" + } + (true, true) => { + loop { + print!("{} Choose config to use (1=default, 2=predownload): ", Status::question()); + io::stdout().flush().map_err(|e| format!("Failed to flush stdout: {}", e))?; + + let mut input = String::new(); + io::stdin() + .read_line(&mut input) + .map_err(|e| format!("Failed to read input: {}", e))?; + + match input.trim() { + "1" => break "default", + "2" => break "predownload", + _ => println!("{} Invalid choice, please enter 1 or 2", Status::error()), + } + } + } + (false, false) => return Err("Neither default.config nor predownload.config found in response".to_string()), + }; + + let config_data = config + .get(selected_config) + .ok_or_else(|| format!("Missing {} config in response", selected_config))?; + + let base_config = config_data + .get("config") + .ok_or_else(|| format!("Missing config in {} response", selected_config))?; + + let base_url = base_config .get("baseUrl") .and_then(Value::as_str) .ok_or("Missing or invalid baseUrl")?; - let index_file = predownload_config + let index_file = base_config .get("indexFile") .and_then(Value::as_str) .ok_or("Missing or invalid indexFile")?; - let default_config = config - .get("default") - .ok_or("Missing default config in response")?; - - let cdn_list = default_config + let cdn_list = config_data .get("cdnList") .and_then(Value::as_array) .ok_or("Missing or invalid cdnList")?;