diff --git a/src/config/status.rs b/src/config/status.rs index f8fc396..bc029e8 100644 --- a/src/config/status.rs +++ b/src/config/status.rs @@ -9,6 +9,6 @@ impl Status { pub fn warning() -> ColoredString { "[!]".yellow() } pub fn error() -> ColoredString { "[-]".red() } pub fn question() -> ColoredString { "[?]".blue() } - pub fn progress() -> ColoredString { "[→]".magenta() } - pub fn matched() -> ColoredString { "[↓]".blue() } + pub fn progress() -> ColoredString { "[→]".purple() } + pub fn matched() -> ColoredString { "[↓]".bright_purple() } } \ No newline at end of file diff --git a/src/io/console.rs b/src/io/console.rs index fb5ec8f..f2736e4 100644 --- a/src/io/console.rs +++ b/src/io/console.rs @@ -1,8 +1,6 @@ -use std::{io, path::Path, time::{Duration, Instant}}; +use std::{io, path::Path}; use colored::Colorize; -use winconsole::console::set_title; -use crate::{config::status::Status, download::progress::DownloadProgress}; -use super::logging::{bytes_to_human, format_duration}; +use crate::config::status::Status; pub fn print_results(success: usize, total: usize, folder: &Path) { let title = if success == total { @@ -30,54 +28,3 @@ pub fn print_results(success: usize, total: usize, folder: &Path) { println!("\n{} Press Enter to exit...", Status::warning()); let _ = io::stdin().read_line(&mut String::new()); } - -pub fn update_title( - start_time: Instant, - success: usize, - total: usize, - progress: &DownloadProgress, -) { - let elapsed = 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 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_bytes = total_bytes.saturating_sub(downloaded_bytes); - let eta_secs = if speed > 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 - {}{} - Speed: {}{} - ETA: {}", - success, - total, - bytes_to_human(downloaded_bytes), - progress_percent, - speed_value, - speed_unit, - eta_str - ); - - set_title(&title).unwrap(); -} \ No newline at end of file diff --git a/src/io/logging.rs b/src/io/logging.rs index 03ed3cf..2640b73 100644 --- a/src/io/logging.rs +++ b/src/io/logging.rs @@ -1,5 +1,11 @@ use std::{fs::{self, OpenOptions}, io::Write, time::{Duration, SystemTime}}; +use colored::Colorize; +use reqwest::blocking::Client; +use serde_json::Value; + +use crate::config::{cfg::Config, status::Status}; + pub fn setup_logging() -> fs::File { OpenOptions::new() .create(true) @@ -28,4 +34,70 @@ pub fn bytes_to_human(bytes: u64) -> String { b if b > 1_000 => format!("{:.2} KB", b as f64 / 1_000.0), b => format!("{} B", b), } -} \ No newline at end of file +} + +pub fn calculate_total_size(resources: &[Value], client: &Client, config: &Config) -> u64 { + let mut total_size = 0; + let mut failed_urls = 0; + + println!("{} Processing files...", Status::info()); + + for (i, item) in resources.iter().enumerate() { + if let Some(dest) = item.get("dest").and_then(Value::as_str) { + let mut file_size = 0; + let mut found_valid_url = false; + + for base_url in &config.zip_bases { + let url = format!("{}/{}", base_url, dest); + match client.head(&url).send() { + Ok(response) => { + if let Some(len) = response.headers().get("content-length") { + if let Ok(len_str) = len.to_str() { + if let Ok(len_num) = len_str.parse::() { + file_size = len_num; + found_valid_url = true; + break; + } + } + } + } + Err(e) => { + println!("{} Failed to HEAD {}: {}", Status::warning(), url, e); + } + } + } + + if found_valid_url { + total_size += file_size; + } else { + failed_urls += 1; + println!("{} Could not determine size for file: {}", Status::error(), dest); + } + } + + if i % 10 == 0 { + println!( + "{} Processed {}/{} files...", + Status::info(), + i + 1, + resources.len() + ); + } + } + + if failed_urls > 0 { + println!( + "{} Warning: Could not determine size for {} files", + Status::warning(), + failed_urls + ); + } + + println!( + "{} Total download size: {}", + Status::info(), + bytes_to_human(total_size).cyan() + ); + + total_size +} diff --git a/src/main.rs b/src/main.rs index 7f37351..83af561 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,27 @@ use colored::*; use reqwest::blocking::Client; use serde_json::Value; -use std::{sync::Arc, time::{Duration, Instant}, io, thread}; -use winconsole::console::clear; +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, update_title}, file::get_dir, logging::{log_error, setup_logging}}, + io::{ + console::print_results, + file::get_dir, + logging::{bytes_to_human, format_duration, log_error, setup_logging, calculate_total_size}, + }, network::client::{download_file, fetch_index, get_predownload}, }; fn main() { + set_title("Wuthering Waves Downloader").unwrap(); let log_file = setup_logging(); let client = Client::new(); @@ -54,13 +64,15 @@ 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 progress = DownloadProgress { - total_bytes: Arc::new(std::sync::atomic::AtomicU64::new(0)), + 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(), }; @@ -70,27 +82,71 @@ fn main() { let progress_clone = progress.clone(); let title_thread = thread::spawn(move || { while !should_stop_clone.load(std::sync::atomic::Ordering::SeqCst) { - update_title( - progress_clone.start_time, - success_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); + + 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 - {}{} - Speed: {}{} - Total ETA: {}", + current_success, total_files, - &progress_clone, + bytes_to_human(downloaded_bytes), + progress_percent, + speed_value, + speed_unit, + eta_str ); + + set_title(&title).unwrap(); thread::sleep(Duration::from_secs(1)); } }); - let success_clone = success.clone(); - let should_stop_clone = should_stop.clone(); - let log_file_clone = log_file.try_clone().unwrap(); - let folder_clone2 = folder.clone(); + let should_stop_ctrlc = should_stop.clone(); + let success_ctrlc = success.clone(); + let folder_ctrlc = folder.clone(); + let log_file_ctrlc = log_file.try_clone().unwrap(); ctrlc::set_handler(move || { - should_stop_clone.store(true, std::sync::atomic::Ordering::SeqCst); + should_stop_ctrlc.store(true, std::sync::atomic::Ordering::SeqCst); clear().unwrap(); println!("{} Download interrupted by user", Status::warning()); - let success_count = success_clone.load(std::sync::atomic::Ordering::SeqCst); + let success_count = success_ctrlc.load(std::sync::atomic::Ordering::SeqCst); let title = if success_count == total_files { " DOWNLOAD COMPLETE ".on_blue().white().bold() @@ -112,7 +168,7 @@ fn main() { println!( "{} Files saved to: {}", Status::info(), - folder_clone2.display().to_string().cyan() + folder_ctrlc.display().to_string().cyan() ); println!("\n{} Press Enter to exit...", Status::warning()); @@ -120,7 +176,7 @@ fn main() { io::stdin().read_line(&mut input).unwrap(); log_error( - &log_file_clone, + &log_file_ctrlc, &format!( "Download interrupted by user. Success: {}/{}", success_count, total_files diff --git a/src/network/client.rs b/src/network/client.rs index 44e24ad..40f77e6 100644 --- a/src/network/client.rs +++ b/src/network/client.rs @@ -2,7 +2,7 @@ use colored::Colorize; use flate2::read::GzDecoder; use reqwest::blocking::Client; use serde_json::{from_reader, from_str, Value}; -use winconsole::console; +use winconsole::console::{self}; use std::{io::{Read, Write}, fs, io, path::Path, time::Duration}; use crate::config::cfg::Config; @@ -137,7 +137,7 @@ pub fn download_file( if let (Some(md5), Some(size)) = (expected_md5, file_size) { if should_skip_download(&path, Some(md5), Some(size)) { - println!("{} File is valid: {}", Status::matched(), filename.blue()); + println!("{} File is valid: {}", Status::matched(), filename.bright_purple()); return true; } } @@ -172,12 +172,12 @@ pub fn download_file( if let (Some(md5), Some(size)) = (expected_md5, expected_size) { if check_existing_file(&path, Some(md5), Some(size)) { - println!("{} File is valid: {}", Status::matched(), filename.blue()); + println!("{} File is valid: {}", Status::matched(), filename.bright_purple()); return true; } } - println!("{} Downloading: {}", Status::progress(), filename.magenta()); + println!("{} Downloading: {}", Status::progress(), filename.purple()); let mut retries = MAX_RETRIES; let mut last_error = None;