diff --git a/Cargo.lock b/Cargo.lock index 11077bc..f2e672c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -91,6 +91,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atomic" version = "0.6.0" @@ -282,6 +293,19 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width", + "windows-sys 0.59.0", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -439,6 +463,12 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -1496,10 +1526,12 @@ dependencies = [ name = "reddit-magnet" version = "0.1.0" dependencies = [ + "async-trait", "chrono", "clap", "clap-verbosity-flag", "color-eyre", + "console", "diesel", "diesel_migrations", "directories", @@ -2281,6 +2313,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "untrusted" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index 1239caa..1f7c02e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,3 +27,5 @@ transmission-rpc = "0.5.0" url = "2.5.4" clap-verbosity-flag = "3.0.2" pretty_env_logger = "0.5.0" +async-trait = "0.1.77" +console = "0.15.8" diff --git a/src/actions/action.rs b/src/actions/action.rs new file mode 100644 index 0000000..9f2c0ae --- /dev/null +++ b/src/actions/action.rs @@ -0,0 +1,22 @@ +use crate::models::Magnet; +use async_trait::async_trait; +use color_eyre::eyre::Result; + +/// Struct to hold the list of processed and failed magnets +pub struct ProcessedMagnets { + pub success: Vec, + pub failed: Vec, +} + +/// Trait for actions that process magnet links +#[async_trait] +pub trait Action { + /// Return the name of the action + fn name(&self) -> &str; + + /// Process all unprocessed magnet links and return the list of processed magnets + async fn process_unprocessed_magnets( + &mut self, + db: &mut crate::db::Database, + ) -> Result; +} diff --git a/src/actions/mod.rs b/src/actions/mod.rs index f43bdff..542d786 100644 --- a/src/actions/mod.rs +++ b/src/actions/mod.rs @@ -1 +1,2 @@ +pub mod action; pub mod transmission; diff --git a/src/actions/transmission/action.rs b/src/actions/transmission/action.rs index 8093d83..2871c48 100644 --- a/src/actions/transmission/action.rs +++ b/src/actions/transmission/action.rs @@ -1,51 +1,63 @@ +use crate::actions::action::{Action, ProcessedMagnets}; use crate::actions::transmission::client::TransmissionClient; use crate::actions::transmission::config::TransmissionConfig; use crate::db::{Database, TransmissionProcessedTable}; use color_eyre::eyre::Result; -use log::{debug, info, warn}; +use log::{debug, warn}; /// Action for submitting magnet links to Transmission pub struct TransmissionAction { client: TransmissionClient, - db: Database, } impl TransmissionAction { - pub async fn new(config: &TransmissionConfig, db: Database) -> Result { + pub async fn new(config: &TransmissionConfig) -> Result { let client = TransmissionClient::new(config)?; - Ok(TransmissionAction { client, db }) + Ok(TransmissionAction { client }) + } +} + +#[async_trait::async_trait] +impl Action for TransmissionAction { + /// Return the name of the action + fn name(&self) -> &str { + "Transmission" } - /// Process all unprocessed magnet links - pub async fn process_unprocessed_magnets(&mut self) -> Result { - let unprocessed_magnets = self - .db - .get_unprocessed_magnets_for_table::()?; - let mut processed_count = 0; + /// Process all unprocessed magnet links and return the list of processed magnets + async fn process_unprocessed_magnets(&mut self, db: &mut Database) -> Result { + let unprocessed_magnets = + db.get_unprocessed_magnets_for_table::()?; + let mut processed_magnets = Vec::new(); + let mut failed_magnets = Vec::new(); for magnet in unprocessed_magnets { if let Some(id) = magnet.id { match self.client.submit_magnet(&magnet.link).await { Ok(_) => { - info!( + debug!( "Successfully submitted magnet link to Transmission: {}", magnet.title ); debug!("Magnet link: {}", magnet.link); - self.db - .mark_magnet_processed_for_table::(id)?; - processed_count += 1; + db.mark_magnet_processed_for_table::(id)?; + processed_magnets.push(magnet); } Err(e) => { warn!("Failed to submit magnet link to Transmission: {}", e); + failed_magnets.push(magnet); } } } else { warn!("Skipping magnet with null ID: {}", magnet.link); + // Consider adding to failed_magnets if we want to report these as well } } - Ok(processed_count) + Ok(ProcessedMagnets { + success: processed_magnets, + failed: failed_magnets, + }) } } diff --git a/src/main.rs b/src/main.rs index 69b1c0d..dadbca7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +use crate::actions::action::Action; use crate::actions::transmission::TransmissionAction; use crate::args::Args; use crate::config::{get_db_path, load_config}; @@ -10,7 +11,7 @@ use log::{debug, info, warn}; use multimap::MultiMap; use reddit_client::RedditClient; use regex::Regex; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::fs::create_dir_all; mod actions; @@ -20,6 +21,7 @@ mod db; mod magnet; mod models; mod reddit_client; +mod report; mod schema; #[derive(Debug)] @@ -39,43 +41,6 @@ fn filter_posts(title: &str, title_filter: Option) -> bool { } } -/// Prints the posts with their magnet links -fn print_posts(posts: &[PostInfo], username: &str, title_filter: Option) -> usize { - println!("Magnet links from u/{}:", username); - if let Some(pattern) = title_filter { - println!("Filtering titles by pattern: {}", pattern); - } - println!("----------------------------"); - - let mut post_count = 0; - - for post in posts { - // Only display posts with magnet links - if !post.magnet_links.is_empty() { - post_count += 1; - - println!( - "{}. [r/{}] {} (Posted: {})", - post_count, - post.subreddit, - post.title, - post.timestamp.format("%Y-%m-%d %H:%M:%S") - ); - - for (i, link) in post.magnet_links.iter().enumerate() { - println!(" Link {}: {}", i + 1, link); - } - println!(); - } - } - - if post_count == 0 { - println!("No posts with magnet links found."); - } - - post_count -} - #[tokio::main] async fn main() -> Result<()> { color_eyre::install()?; @@ -117,8 +82,9 @@ async fn main() -> Result<()> { } // Process sources and store magnet links + let mut total_new_links = 0; for (source_name, source_config) in conf.sources { - println!("\nProcessing source [{}]", source_name); + info!("Processing source [{}]", source_name); let username = source_config.username.clone(); let title_filter = match source_config.title_filter { @@ -130,8 +96,6 @@ async fn main() -> Result<()> { }; if let Some(submissions) = user_posts.get_vec(&username) { - let mut filtered_posts = Vec::new(); - for post in submissions .iter() .filter(|s| filter_posts(&*s.title, title_filter.clone())) @@ -151,35 +115,29 @@ async fn main() -> Result<()> { }; // Store the post info in the database - if let Err(e) = db.store_magnets(&post_info) { - warn!("Failed to store post info in database: {}", e); + match db.store_magnets(&post_info) { + Ok(count) => { + total_new_links += count; + } + Err(e) => { + warn!("Failed to store post info in database: {}", e); + } } - - filtered_posts.push(post_info); } } - - print_posts(&filtered_posts, &username, title_filter); } } - // Process magnet links with Transmission if enabled + // Initialize actions + let mut actions: Vec> = Vec::new(); + + // Add Transmission action if enabled if let Some(transmission_config) = conf.transmission { if transmission_config.enable { - info!("Processing magnet links with Transmission"); - match TransmissionAction::new(&transmission_config, db).await { - Ok(mut transmission_action) => { - match transmission_action.process_unprocessed_magnets().await { - Ok(count) => { - info!( - "Successfully processed {} magnet links with Transmission", - count - ); - } - Err(e) => { - warn!("Failed to process magnet links with Transmission: {}", e); - } - } + info!("Initializing Transmission action"); + match TransmissionAction::new(&transmission_config).await { + Ok(transmission_action) => { + actions.push(Box::new(transmission_action)); } Err(e) => { warn!("Failed to initialize Transmission action: {}", e); @@ -192,5 +150,32 @@ async fn main() -> Result<()> { debug!("No Transmission configuration found"); } + // Process all actions and collect results + let mut action_results = HashMap::new(); + + for action in &mut actions { + let action_name = action.name().to_string(); + debug!("Processing magnet links with {} action", action_name); + + match action.process_unprocessed_magnets(&mut db).await { + Ok(processed_magnets) => { + let count = processed_magnets.success.len(); + debug!( + "Successfully processed {} magnet links with {}", + count, action_name + ); + action_results.insert(action_name, processed_magnets); + } + Err(e) => { + warn!("Failed to process magnet links with {}: {}", action_name, e); + } + } + } + + // Generate report + if let Err(e) = report::generate_report(&action_results, total_new_links) { + warn!("Failed to generate report: {}", e); + } + Ok(()) } diff --git a/src/report.rs b/src/report.rs new file mode 100644 index 0000000..a5e99e7 --- /dev/null +++ b/src/report.rs @@ -0,0 +1,87 @@ +use crate::actions::action::ProcessedMagnets; +use color_eyre::eyre::Result; +use console::{style, Emoji}; +use log::info; +use std::collections::HashMap; + +static SPARKLES: Emoji = Emoji("✨", ":sparkles:"); +static ROCKET: Emoji = Emoji("🚀", ":rocket:"); +static LINK: Emoji = Emoji("🔗", ":link:"); +static WARNING: Emoji = Emoji("⚠️", ":warning:"); + +/// Generate a report of processed magnets +pub fn generate_report( + action_results: &HashMap, + total_new_links: usize, +) -> Result<()> { + info!(""); + info!( + "{} {} {}", + SPARKLES, + style("Report Summary").bold().underlined(), + style(format!( + "{} new links added to the database", + total_new_links + )) + .bold() + .green(), + ); + info!(""); + + for (action_name, processed_magnets) in action_results { + let success_count = processed_magnets.success.len(); + let failed_count = processed_magnets.failed.len(); + let total_count = success_count + failed_count; + + // Section header for each action + info!(""); + info!( + "{} {} {}", + ROCKET, + style(format!("{}", action_name)).bold().underlined().cyan(), + style(format!("({} new links)", total_count)).bold() + ); + + // Success/failure summary + if failed_count > 0 { + info!( + " {} Success: {}, {} Failure: {}", + style("✅").green(), + style(success_count).bold().green(), + style("❌").red(), + style(failed_count).bold().red() + ); + } else { + info!( + " {} Success: {}", + style("✅").green(), + style(success_count).bold().green() + ); + } + info!(""); + + // List successful magnets + if success_count > 0 { + info!( + " {} {}", + style("✅").green(), + style("Added:").bold().green() + ); + for magnet in &processed_magnets.success { + info!(" {} {}", LINK, style(&magnet.title).italic()); + } + info!(""); + } + + // List failed magnets + if failed_count > 0 { + info!(" {} {}", style("❌").red(), style("Failed:").bold().red()); + for magnet in &processed_magnets.failed { + info!(" {} {}", WARNING, style(&magnet.title).italic()); + } + info!(""); + } + } + + Ok(()) +}