mirror of
https://github.com/yuhkix/wuwa-downloader.git
synced 2025-06-04 08:53:41 +00:00
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
This commit is contained in:
parent
a4e0248ea5
commit
06e10b1f78
3 changed files with 190 additions and 140 deletions
122
src/io/util.rs
122
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<String
|
|||
.as_str()
|
||||
.map(|s| s.to_string())
|
||||
.ok_or_else(|| format!("Missing {} URL", version))
|
||||
}
|
||||
|
||||
pub fn exit_with_error(log_file: &File, error: &str) -> ! {
|
||||
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<std::sync::atomic::AtomicBool>,
|
||||
Arc<std::sync::atomic::AtomicUsize>,
|
||||
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<std::sync::atomic::AtomicBool>,
|
||||
success: Arc<std::sync::atomic::AtomicUsize>,
|
||||
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<std::sync::atomic::AtomicBool>) {
|
||||
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<std::sync::atomic::AtomicBool>,
|
||||
progress: &DownloadProgress,
|
||||
success: &Arc<std::sync::atomic::AtomicUsize>,
|
||||
) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
153
src/main.rs
153
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,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -291,7 +291,7 @@ fn download_single_file(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_predownload(client: &Client) -> Result<Config, String> {
|
||||
pub fn get_config(client: &Client) -> Result<Config, String> {
|
||||
let selected_index_url = fetch_gist(client)?;
|
||||
println!("{} Fetching download configuration...", Status::info());
|
||||
|
||||
|
@ -327,26 +327,57 @@ pub fn get_predownload(client: &Client) -> Result<Config, String> {
|
|||
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")?;
|
||||
|
|
Loading…
Reference in a new issue