From 38b5d5bb32a693bebd93f7a6cb5cc766a18a2b9b Mon Sep 17 00:00:00 2001 From: Kienan Stewart Date: Sun, 25 Sep 2022 17:11:48 -0400 Subject: [PATCH] Split Job struct into it's own module --- src/job.rs | 187 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 101 ++-------------------------- 2 files changed, 191 insertions(+), 97 deletions(-) create mode 100644 src/job.rs diff --git a/src/job.rs b/src/job.rs new file mode 100644 index 0000000..1c33588 --- /dev/null +++ b/src/job.rs @@ -0,0 +1,187 @@ +use std::path::{Path,PathBuf}; +use std::str::FromStr; +use std::time::Duration; +use std::time::Instant; + +use ansi_to_html; +use chrono; +use rss; + +use crate::conf::Conf; + +pub struct Job { + pub url: String, + pub selector: String, + pub every: Duration, + pub last_run: Option, + pub output_file: Option, + pub channel: Option, +} + +impl Job { + + fn default(conf: &Conf) -> Job { + return Job { + url: "".to_string(), + selector: "".to_string(), + every: conf.check_interval, + last_run: None, + output_file: None, + channel: None, + }; + } + + pub fn new(url: &str, selector: &str, conf: &Conf) -> Job { + let mut job = Job::default(conf); + job.url = url.to_string(); + job.selector = selector.to_string(); + let mut output_file = conf.output_dir.clone(); + let mut file_name = job.url.clone().replace("/", "-"); + file_name.push_str(".rss"); + output_file = output_file.join(Path::new(&file_name)); + job.output_file = Some(output_file); + + match std::fs::File::open(job.output_file.as_ref().unwrap()) { + Err(why) => { + println!("Failed to open '{}': {}", job.output_file.as_ref().unwrap().display(), why); + println!("Creating empty RSS channel for job '{}'", job.url); + job.channel = Some( + rss::ChannelBuilder::default() + .title(url) + .link(url) + .description("haunting") + .build() + ); + job.channel.as_mut().unwrap().set_generator("Haunter".to_string()); + }, + Ok(file) => { + job.channel = Some( + rss::Channel::read_from(std::io::BufReader::new(file)).unwrap() + ); + }, + }; + return job; + } + + pub fn from_file<'a>(path: &'a Path, conf: &'a Conf) -> Result { + let mut job = Job::default(conf); + let items = match crate::conf::read_conf_file(path) { + Err(_) => return Err("Failed to read from configuration file"), + Ok(items) => items, + }; + for item in items.iter() { + let key = item.0.as_str(); + match key { + "url" => { + job.url = item.1.clone(); + }, + "selector" => { + job.selector = item.1.clone(); + }, + "every" => { + let converted_value = match item.1.parse::() { + Err(why) => { + println!("Failed to convert '{}' to u64: {}", item.1, why); + return Err("Failed to parse value of 'every'"); + }, + Ok(v) => v, + }; + job.every = Duration::new(converted_value, 0); + }, + "output_file" => { + job.output_file = Some( + conf.output_dir.join(PathBuf::from_str(item.1.as_str()).unwrap()) + ); + } + _ => { + println!("Unknown key '{}' in job file '{}'", key, path.display()); + return Err("Unknown key"); + } + } + } + return Ok(job); + } + + pub fn update(&mut self, value: &str, diff: &str) { + if self.channel.is_none() { + println!("Skipping update of channel: no channel set"); + return; + } + let channel = self.channel.as_mut().unwrap(); + let update_time = chrono::Utc::now(); + let item = rss::ItemBuilder::default() + .title(format!("Update to '{}'", self.url)) + .link(self.url.clone()) + .pub_date(update_time.to_rfc2822()) + .content(format!(r#" +New content at {}:
+
+{}
+
+

+Diff:
+
+{}
+
+"#, + update_time.format("%d/%m/%Y %H:%M"), + ansi_to_html::convert_escaped(value).unwrap().as_str(), + ansi_to_html::convert_escaped(diff).unwrap().as_str() + ) + ) + .build(); + + channel.items.push(item); + + if self.output_file.is_some() { + match std::fs::File::create(self.output_file.as_ref().unwrap()) { + Err(why) => { + println!("Failed to open '{}' for writing: {}", self.output_file.as_ref().unwrap().display(), why); + }, + Ok(file) => { + channel.write_to( + std::io::BufWriter::new(file) + ).expect("Failed to write updated channel"); + }, + }; + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use std::io::Write; + use tempfile::NamedTempFile; + + #[test] + fn create() { + let conf = Conf::get_default_conf(); + let job = Job::new(&"my/url", &"myselector", &conf); + assert_eq!(job.url, "my/url"); + assert_eq!(job.output_file.unwrap().to_str().unwrap(), "results.d/my-url.rss"); + assert_eq!(job.selector, "myselector"); + } + + #[test] + fn create_from_file() { + let conf = Conf::get_default_conf(); + let mut tf = NamedTempFile::new().unwrap(); + let job_conf = r#" +url = http://example.com/test +output_file = example_output.atom +every=7200 + +selector = section.listing:nth-child(2) > ul:nth-child(1) > li:nth-child(3) > header:nth-child(3) > h2:nth-child(1) > a:nth-child(1) +"#; + tf.write_all(job_conf.as_bytes()).expect("Failed to write configuration to file"); + + let job = Job::from_file(tf.path(), &conf).expect("Failed to read configuration file"); + assert_eq!(job.url, "http://example.com/test"); + assert_eq!(job.output_file.unwrap().to_str().unwrap(), "results.d/example_output.atom"); + assert_eq!(job.every.as_secs(), 7200); + assert_eq!(job.selector, "section.listing:nth-child(2) > ul:nth-child(1) > li:nth-child(3) > header:nth-child(3) > h2:nth-child(1) > a:nth-child(1)"); + } + +} diff --git a/src/main.rs b/src/main.rs index 5f8676f..1114318 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,111 +1,17 @@ use std::cmp::Ordering; use std::env; -use std::path::{Path,PathBuf}; -use std::str::FromStr; +use std::path::Path; use std::thread; use std::time::Duration; use std::time::Instant; -use ansi_to_html; -use chrono; -use rss; use thirtyfour_sync::WebDriverCommands; mod conf; use conf::Conf; -struct Job { - url: String, - selector: String, - every: Duration, - last_run: Option, - output_file: Option, - channel: Option, -} - -impl Job { - fn new(url: &str, selector: &str, conf: &Conf) -> Job { - let mut job = Job { - url: String::from_str(url).unwrap(), - selector: String::from_str(selector).unwrap(), - every: conf.check_interval, - last_run: None, - output_file: None, - channel: None, - }; - let mut output_file = conf.output_dir.clone(); - let mut file_name = job.url.clone().replace("/", "-"); - file_name.push_str(".rss"); - output_file = output_file.join(Path::new(&file_name)); - job.output_file = Some(output_file); - - match std::fs::File::open(job.output_file.as_ref().unwrap()) { - Err(why) => { - println!("Failed to open '{}': {}", job.output_file.as_ref().unwrap().display(), why); - println!("Creating empty RSS channel for job '{}'", job.url); - job.channel = Some( - rss::ChannelBuilder::default() - .title(url) - .link(url) - .description("haunting") - .build() - ); - job.channel.as_mut().unwrap().set_generator("Haunter".to_string()); - }, - Ok(file) => { - job.channel = Some( - rss::Channel::read_from(std::io::BufReader::new(file)).unwrap() - ); - }, - }; - return job; - } - - fn update(&mut self, value: &str, diff: &str) { - if self.channel.is_none() { - println!("Skipping update of channel: no channel set"); - return; - } - let channel = self.channel.as_mut().unwrap(); - let update_time = chrono::Utc::now(); - let item = rss::ItemBuilder::default() - .title(format!("Update to '{}'", self.url)) - .link(self.url.clone()) - .pub_date(update_time.to_rfc2822()) - .content(format!(r#" -New content at {}:
-
-{}
-
-

-Diff:
-
-{}
-
-"#, - update_time.format("%d/%m/%Y %H:%M"), - ansi_to_html::convert_escaped(value).unwrap().as_str(), - ansi_to_html::convert_escaped(diff).unwrap().as_str() - ) - ) - .build(); - - channel.items.push(item); - - if self.output_file.is_some() { - match std::fs::File::create(self.output_file.as_ref().unwrap()) { - Err(why) => { - println!("Failed to open '{}' for writing: {}", self.output_file.as_ref().unwrap().display(), why); - }, - Ok(file) => { - channel.write_to( - std::io::BufWriter::new(file) - ).expect("Failed to write updated channel"); - }, - }; - } - } -} +mod job; +use job::Job; struct ThreadJob { job: Job, @@ -265,6 +171,7 @@ fn main() { } break; } + std::thread::sleep(Duration::new(1, 0)); } }