Merge branch 'report' into 'main'

Clean up output

See merge request kernald/reddit-magnet!8
This commit is contained in:
Marc Plano-Lesay 2025-05-01 08:34:59 +00:00
commit 90c3fc5ee3
7 changed files with 224 additions and 77 deletions

38
Cargo.lock generated
View file

@ -91,6 +91,17 @@ dependencies = [
"windows-sys 0.59.0", "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]] [[package]]
name = "atomic" name = "atomic"
version = "0.6.0" version = "0.6.0"
@ -282,6 +293,19 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 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]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.4" version = "0.9.4"
@ -439,6 +463,12 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "encode_unicode"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.35" version = "0.8.35"
@ -1496,10 +1526,12 @@ dependencies = [
name = "reddit-magnet" name = "reddit-magnet"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"async-trait",
"chrono", "chrono",
"clap", "clap",
"clap-verbosity-flag", "clap-verbosity-flag",
"color-eyre", "color-eyre",
"console",
"diesel", "diesel",
"diesel_migrations", "diesel_migrations",
"directories", "directories",
@ -2281,6 +2313,12 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "unicode-width"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
[[package]] [[package]]
name = "untrusted" name = "untrusted"
version = "0.9.0" version = "0.9.0"

View file

@ -27,3 +27,5 @@ transmission-rpc = "0.5.0"
url = "2.5.4" url = "2.5.4"
clap-verbosity-flag = "3.0.2" clap-verbosity-flag = "3.0.2"
pretty_env_logger = "0.5.0" pretty_env_logger = "0.5.0"
async-trait = "0.1.77"
console = "0.15.8"

22
src/actions/action.rs Normal file
View file

@ -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<Magnet>,
pub failed: Vec<Magnet>,
}
/// 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<ProcessedMagnets>;
}

View file

@ -1 +1,2 @@
pub mod action;
pub mod transmission; pub mod transmission;

View file

@ -1,51 +1,63 @@
use crate::actions::action::{Action, ProcessedMagnets};
use crate::actions::transmission::client::TransmissionClient; use crate::actions::transmission::client::TransmissionClient;
use crate::actions::transmission::config::TransmissionConfig; use crate::actions::transmission::config::TransmissionConfig;
use crate::db::{Database, TransmissionProcessedTable}; use crate::db::{Database, TransmissionProcessedTable};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use log::{debug, info, warn}; use log::{debug, warn};
/// Action for submitting magnet links to Transmission /// Action for submitting magnet links to Transmission
pub struct TransmissionAction { pub struct TransmissionAction {
client: TransmissionClient, client: TransmissionClient,
db: Database,
} }
impl TransmissionAction { impl TransmissionAction {
pub async fn new(config: &TransmissionConfig, db: Database) -> Result<Self> { pub async fn new(config: &TransmissionConfig) -> Result<Self> {
let client = TransmissionClient::new(config)?; 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 /// Process all unprocessed magnet links and return the list of processed magnets
pub async fn process_unprocessed_magnets(&mut self) -> Result<usize> { async fn process_unprocessed_magnets(&mut self, db: &mut Database) -> Result<ProcessedMagnets> {
let unprocessed_magnets = self let unprocessed_magnets =
.db db.get_unprocessed_magnets_for_table::<TransmissionProcessedTable>()?;
.get_unprocessed_magnets_for_table::<TransmissionProcessedTable>()?; let mut processed_magnets = Vec::new();
let mut processed_count = 0; let mut failed_magnets = Vec::new();
for magnet in unprocessed_magnets { for magnet in unprocessed_magnets {
if let Some(id) = magnet.id { if let Some(id) = magnet.id {
match self.client.submit_magnet(&magnet.link).await { match self.client.submit_magnet(&magnet.link).await {
Ok(_) => { Ok(_) => {
info!( debug!(
"Successfully submitted magnet link to Transmission: {}", "Successfully submitted magnet link to Transmission: {}",
magnet.title magnet.title
); );
debug!("Magnet link: {}", magnet.link); debug!("Magnet link: {}", magnet.link);
self.db db.mark_magnet_processed_for_table::<TransmissionProcessedTable>(id)?;
.mark_magnet_processed_for_table::<TransmissionProcessedTable>(id)?; processed_magnets.push(magnet);
processed_count += 1;
} }
Err(e) => { Err(e) => {
warn!("Failed to submit magnet link to Transmission: {}", e); warn!("Failed to submit magnet link to Transmission: {}", e);
failed_magnets.push(magnet);
} }
} }
} else { } else {
warn!("Skipping magnet with null ID: {}", magnet.link); 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,
})
} }
} }

View file

@ -1,3 +1,4 @@
use crate::actions::action::Action;
use crate::actions::transmission::TransmissionAction; use crate::actions::transmission::TransmissionAction;
use crate::args::Args; use crate::args::Args;
use crate::config::{get_db_path, load_config}; use crate::config::{get_db_path, load_config};
@ -10,7 +11,7 @@ use log::{debug, info, warn};
use multimap::MultiMap; use multimap::MultiMap;
use reddit_client::RedditClient; use reddit_client::RedditClient;
use regex::Regex; use regex::Regex;
use std::collections::HashSet; use std::collections::{HashMap, HashSet};
use std::fs::create_dir_all; use std::fs::create_dir_all;
mod actions; mod actions;
@ -20,6 +21,7 @@ mod db;
mod magnet; mod magnet;
mod models; mod models;
mod reddit_client; mod reddit_client;
mod report;
mod schema; mod schema;
#[derive(Debug)] #[derive(Debug)]
@ -39,43 +41,6 @@ fn filter_posts(title: &str, title_filter: Option<Regex>) -> bool {
} }
} }
/// Prints the posts with their magnet links
fn print_posts(posts: &[PostInfo], username: &str, title_filter: Option<Regex>) -> 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] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
color_eyre::install()?; color_eyre::install()?;
@ -117,8 +82,9 @@ async fn main() -> Result<()> {
} }
// Process sources and store magnet links // Process sources and store magnet links
let mut total_new_links = 0;
for (source_name, source_config) in conf.sources { 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 username = source_config.username.clone();
let title_filter = match source_config.title_filter { 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) { if let Some(submissions) = user_posts.get_vec(&username) {
let mut filtered_posts = Vec::new();
for post in submissions for post in submissions
.iter() .iter()
.filter(|s| filter_posts(&*s.title, title_filter.clone())) .filter(|s| filter_posts(&*s.title, title_filter.clone()))
@ -151,35 +115,29 @@ async fn main() -> Result<()> {
}; };
// Store the post info in the database // Store the post info in the database
if let Err(e) = db.store_magnets(&post_info) { match db.store_magnets(&post_info) {
warn!("Failed to store post info in database: {}", e); 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<Box<dyn Action>> = Vec::new();
// Add Transmission action if enabled
if let Some(transmission_config) = conf.transmission { if let Some(transmission_config) = conf.transmission {
if transmission_config.enable { if transmission_config.enable {
info!("Processing magnet links with Transmission"); info!("Initializing Transmission action");
match TransmissionAction::new(&transmission_config, db).await { match TransmissionAction::new(&transmission_config).await {
Ok(mut transmission_action) => { Ok(transmission_action) => {
match transmission_action.process_unprocessed_magnets().await { actions.push(Box::new(transmission_action));
Ok(count) => {
info!(
"Successfully processed {} magnet links with Transmission",
count
);
}
Err(e) => {
warn!("Failed to process magnet links with Transmission: {}", e);
}
}
} }
Err(e) => { Err(e) => {
warn!("Failed to initialize Transmission action: {}", e); warn!("Failed to initialize Transmission action: {}", e);
@ -192,5 +150,32 @@ async fn main() -> Result<()> {
debug!("No Transmission configuration found"); 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(()) Ok(())
} }

87
src/report.rs Normal file
View file

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