added proper eta calculation and file processing

This commit is contained in:
Yuhki 2025-03-26 03:38:15 +01:00
parent bc558a8714
commit 895e285c00
5 changed files with 153 additions and 78 deletions

View file

@ -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() }
}

View file

@ -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();
}

View file

@ -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)
@ -29,3 +35,69 @@ pub fn bytes_to_human(bytes: u64) -> String {
b => format!("{} B", b),
}
}
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::<u64>() {
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
}

View file

@ -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

View file

@ -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;