mirror of
https://github.com/ApfelTeeSaft/ia-get.git
synced 2026-08-26 19:23:36 +00:00
refactor: move signal handling into downloader module
- Relocate signal handler setup from main to downloader module - Encapsulate signal handling as internal concern of download operations - Simplify main function by removing signal-related complexity - Reduce coupling between application initialization and download control
This commit is contained in:
committed by
Martin Wimpress
parent
010d20241d
commit
e8db2e1d91
+71
-4
@@ -18,6 +18,22 @@ const BUFFER_SIZE: usize = 8192;
|
||||
/// File size threshold for showing hash progress bar (16MB)
|
||||
const LARGE_FILE_THRESHOLD: u64 = 16 * 1024 * 1024;
|
||||
|
||||
/// Sets up signal handling for graceful shutdown on Ctrl+C
|
||||
///
|
||||
/// Returns an Arc<AtomicBool> that can be checked to see if the process
|
||||
/// should stop. When Ctrl+C is pressed, this will be set to false.
|
||||
fn setup_signal_handler() -> Arc<AtomicBool> {
|
||||
let running = Arc::new(AtomicBool::new(true));
|
||||
let r = running.clone();
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
r.store(false, Ordering::SeqCst);
|
||||
println!("\nReceived Ctrl+C, finishing current operation...");
|
||||
}).expect("Error setting Ctrl+C handler");
|
||||
|
||||
running
|
||||
}
|
||||
|
||||
/// Calculates the MD5 hash of a file
|
||||
fn calculate_md5(file_path: &str, running: &Arc<AtomicBool>) -> Result<String> {
|
||||
let file = File::open(file_path)?;
|
||||
@@ -218,17 +234,22 @@ fn verify_downloaded_file(file_path: &str, expected_md5: Option<&str>, running:
|
||||
}
|
||||
|
||||
/// Download a file from archive.org with resume capability
|
||||
///
|
||||
/// This function handles signal setup internally and manages graceful shutdown
|
||||
/// during download operations.
|
||||
pub async fn download_file(
|
||||
client: &Client,
|
||||
url: &str,
|
||||
file_path: &str,
|
||||
expected_md5: Option<&str>,
|
||||
running: &Arc<AtomicBool>
|
||||
) -> Result<()> {
|
||||
// Set up signal handling for this download session
|
||||
let running = setup_signal_handler();
|
||||
|
||||
println!(" ");
|
||||
println!("📦️ Filename {}", file_path);
|
||||
|
||||
if let Some(is_valid) = check_existing_file(file_path, expected_md5, running)? {
|
||||
if let Some(is_valid) = check_existing_file(file_path, expected_md5, &running)? {
|
||||
if is_valid {
|
||||
println!("✅ File already exists and is valid: {}", file_path);
|
||||
return Ok(());
|
||||
@@ -243,8 +264,54 @@ pub async fn download_file(
|
||||
|
||||
let file_size = file.metadata()?.len();
|
||||
let is_resuming = file_size > 0;
|
||||
download_file_content(client, url, file_size, &mut file, running, is_resuming).await?;
|
||||
verify_downloaded_file(file_path, expected_md5, running)?;
|
||||
download_file_content(client, url, file_size, &mut file, &running, is_resuming).await?;
|
||||
verify_downloaded_file(file_path, expected_md5, &running)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Download multiple files with shared signal handling
|
||||
///
|
||||
/// This function sets up signal handling once for the entire download session
|
||||
/// and allows for graceful interruption between files.
|
||||
pub async fn download_files<I>(
|
||||
client: &Client,
|
||||
files: I,
|
||||
) -> Result<()>
|
||||
where
|
||||
I: IntoIterator<Item = (String, String, Option<String>)>, // (url, filename, md5)
|
||||
{
|
||||
// Set up signal handling for the entire download session
|
||||
let running = setup_signal_handler();
|
||||
|
||||
for (url, file_path, expected_md5) in files {
|
||||
// Check if we should stop due to signal
|
||||
if !running.load(Ordering::SeqCst) {
|
||||
println!("\nDownload interrupted. Run the command again to resume remaining files.");
|
||||
break;
|
||||
}
|
||||
|
||||
println!(" ");
|
||||
println!("📦️ Filename {}", file_path);
|
||||
|
||||
if let Some(is_valid) = check_existing_file(&file_path, expected_md5.as_deref(), &running)? {
|
||||
if is_valid {
|
||||
println!("✅ File already exists and is valid: {}", file_path);
|
||||
continue;
|
||||
} else {
|
||||
println!("🔄 File exists but is invalid, re-downloading: {}", file_path);
|
||||
}
|
||||
}
|
||||
|
||||
ensure_parent_directories(&file_path)?;
|
||||
|
||||
let mut file = prepare_file_for_download(&file_path)?;
|
||||
|
||||
let file_size = file.metadata()?.len();
|
||||
let is_resuming = file_size > 0;
|
||||
download_file_content(client, &url, file_size, &mut file, &running, is_resuming).await?;
|
||||
verify_downloaded_file(&file_path, expected_md5.as_deref(), &running)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+7
-43
@@ -15,8 +15,6 @@ use reqwest::Client;
|
||||
use serde_xml_rs::from_str;
|
||||
use clap::Parser;
|
||||
use std::process;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use ia_get::archive_metadata::XmlFiles;
|
||||
|
||||
/// Checks if a URL is accessible by sending a HEAD request
|
||||
@@ -59,34 +57,14 @@ struct Cli {
|
||||
url: String,
|
||||
}
|
||||
|
||||
/// Sets up signal handling for graceful shutdown on Ctrl+C
|
||||
///
|
||||
/// Returns an Arc<AtomicBool> that can be checked to see if the process
|
||||
/// should stop. When Ctrl+C is pressed, this will be set to false.
|
||||
fn setup_signal_handler() -> Arc<AtomicBool> {
|
||||
let running = Arc::new(AtomicBool::new(true));
|
||||
let r = running.clone();
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
r.store(false, Ordering::SeqCst);
|
||||
println!("\nReceived Ctrl+C, finishing current operation...");
|
||||
}).expect("Error setting Ctrl+C handler");
|
||||
|
||||
running
|
||||
}
|
||||
|
||||
/// Main application entry point
|
||||
///
|
||||
/// Parses command line arguments, validates the archive.org URL, checks URL accessibility,
|
||||
/// downloads XML metadata, and iterates through files to download them with resume capability
|
||||
/// and hash verification.
|
||||
/// downloads XML metadata, and initiates file downloads with built-in signal handling.
|
||||
#[tokio::main]
|
||||
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
// Set up signal handling for graceful shutdown
|
||||
let running = setup_signal_handler();
|
||||
|
||||
// Create a single client instance for all requests
|
||||
let client = Client::builder()
|
||||
.user_agent(USER_AGENT)
|
||||
@@ -150,31 +128,17 @@ async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||
);
|
||||
spinner.finish();
|
||||
|
||||
// Iterate over the XML files struct and download each file
|
||||
for file in files.files {
|
||||
// Check if we should stop due to signal
|
||||
if !running.load(Ordering::SeqCst) {
|
||||
println!("\nDownload interrupted. Run the command again to resume remaining files.");
|
||||
break;
|
||||
}
|
||||
|
||||
// Create a clone of the base URL
|
||||
// Prepare download data for batch processing
|
||||
let download_data = files.files.into_iter().map(|file| {
|
||||
let mut absolute_url = base_url.clone();
|
||||
|
||||
// If the URL is relative, join it with the base_url to make it absolute
|
||||
if let Ok(joined_url) = absolute_url.join(&file.name) {
|
||||
absolute_url = joined_url;
|
||||
}
|
||||
(absolute_url.to_string(), file.name, file.md5)
|
||||
}).collect::<Vec<_>>();
|
||||
|
||||
// Download the file
|
||||
downloader::download_file( // Updated to use downloader::download_file
|
||||
&client,
|
||||
absolute_url.as_str(),
|
||||
&file.name,
|
||||
file.md5.as_deref(),
|
||||
&running
|
||||
).await?;
|
||||
}
|
||||
// Download all files with integrated signal handling
|
||||
downloader::download_files(&client, download_data).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user