Split Job struct into it's own module

This commit is contained in:
Kienan Stewart 2022-09-25 17:11:48 -04:00
parent 514209dae2
commit 38b5d5bb32
2 changed files with 191 additions and 97 deletions

187
src/job.rs Normal file
View File

@ -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<Instant>,
pub output_file: Option<PathBuf>,
pub channel: Option<rss::Channel>,
}
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<Job, &'a str> {
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::<u64>() {
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 {}: <br>
<pre class="new-value">
{}
</pre>
<br><br>
Diff: <br>
<pre class="diff-value">
{}
</pre>
"#,
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)");
}
}

View File

@ -1,111 +1,17 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use std::env; use std::env;
use std::path::{Path,PathBuf}; use std::path::Path;
use std::str::FromStr;
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;
use std::time::Instant; use std::time::Instant;
use ansi_to_html;
use chrono;
use rss;
use thirtyfour_sync::WebDriverCommands; use thirtyfour_sync::WebDriverCommands;
mod conf; mod conf;
use conf::Conf; use conf::Conf;
struct Job { mod job;
url: String, use job::Job;
selector: String,
every: Duration,
last_run: Option<Instant>,
output_file: Option<PathBuf>,
channel: Option<rss::Channel>,
}
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 {}: <br>
<pre class="new-value">
{}
</pre>
<br><br>
Diff: <br>
<pre class="diff-value">
{}
</pre>
"#,
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");
},
};
}
}
}
struct ThreadJob { struct ThreadJob {
job: Job, job: Job,
@ -265,6 +171,7 @@ fn main() {
} }
break; break;
} }
std::thread::sleep(Duration::new(1, 0)); std::thread::sleep(Duration::new(1, 0));
} }
} }