use anyhow::{Context, Result, bail}; use std::fs; use tracing::{info, instrument}; pub mod cli; pub mod config; pub mod downloader; use cli::Args; use config::Config; /// The main application logic. #[instrument(skip_all, fields( config_file = %args.input.display(), output_dir = %args.output.display() ))] pub async fn run(args: Args) -> Result<()> { info!("Starting ruleset processor"); // Load and parse the configuration file. let config = Config::load(&args.input) .with_context(|| format!("Failed to load config from {}", args.input.display()))?; // Ensure the output directory exists. fs::create_dir_all(&args.output).with_context(|| { format!( "Failed to create output directory '{}'", args.output.display() ) })?; // Determine the set of files that should exist. let expected_files = config .get_expected_files(&args.output) .context("Failed to determine expected files from config")?; // Clean up any stale files. downloader::cleanup_stale_files(&args.output, &expected_files)?; // Proceed to download files defined in the config. let urls_to_download = config.extract_urls(); if urls_to_download.is_empty() { info!("No rule sets with URLs found in config. Process complete."); return Ok(()); } info!( count = urls_to_download.len(), "Found rule sets to download/update." ); // Download all files concurrently. let download_report = downloader::download_all_rules(&urls_to_download, &args.output, args.concurrency).await?; info!( successful = download_report.successful, failed = download_report.failed, "Download process finished." ); // If any downloads failed, abort with an error message. if download_report.failed > 0 { bail!( "Aborting due to {} download failures.", download_report.failed ); } info!("Ruleset synchronization completed successfully."); Ok(()) }