Split Job struct into it's own module
This commit is contained in:
parent
514209dae2
commit
38b5d5bb32
|
@ -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)");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
101
src/main.rs
101
src/main.rs
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue