Build generic site update checker with my cool & hip new actor system

This commit is contained in:
asonix 2021-06-07 23:01:09 -05:00
commit a956e41001
4 changed files with 1356 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

1182
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

17
Cargo.toml Normal file
View file

@ -0,0 +1,17 @@
[package]
name = "site-checker"
version = "0.1.0"
authors = ["asonix <asonix@asonix.dog>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1"
bytes = "1"
env_logger = "0.8"
log = "0.4"
once_cell = "1.7.2"
reqwest = { version = "0.11", features = ["rustls"] }
tokio = { version = "1", features = ["full"] }
tokio-actors = { version = "0.1.0", git = "https://git.asonix.dog/asonix/tokio-actors", branch = "main" }

156
src/main.rs Normal file
View file

@ -0,0 +1,156 @@
use bytes::Bytes;
use reqwest::{Client, StatusCode};
use std::time::Duration;
static ANE_HREF: &'static str = "https://www.anthronewengland.com/";
static MFF_HREF: &'static str = "https://www.furfest.org/";
static TFF_HREF: &'static str = "https://2022.furryfiesta.org";
static SITES: &'static [(&'static str, &'static str)] = &[
("Anthro New England", ANE_HREF),
("Midwest FurFest", MFF_HREF),
("Texas Furry Fiesta", TFF_HREF),
];
#[derive(Debug)]
enum SiteMessage {
Check,
}
#[derive(Debug)]
struct SiteState {
name: String,
href: String,
client: Client,
result: Option<SiteResult>,
}
#[derive(Debug)]
struct SiteResult {
status: StatusCode,
bytes: Bytes,
}
struct SiteResultDiff {
status: Option<StatusCode>,
bytes: bool,
}
impl SiteState {
async fn check(&mut self) -> anyhow::Result<()> {
log::info!("Checking {}", self.name);
let response = self
.client
.get(&self.href)
.header("Accept", "text/html")
.send()
.await?;
let status = response.status();
let bytes = response.bytes().await?;
let new_result = SiteResult { status, bytes };
let diff = self.result.as_ref().map(|result| result.diff(&new_result));
if let Some(diff) = diff {
if diff.is_different() {
let title = format!("{} Updated", self.name);
let description = if let Some(status) = diff.status {
if diff.bytes {
format!("New status '{}' and site content changed", status)
} else {
format!("New status '{}'", status)
}
} else {
format!("Site content changed")
};
log::info!("{}", title);
log::info!("{}", description);
tokio::process::Command::new("notify-send")
.args(&["-i", "appointment", &title, &description])
.spawn()?
.wait()
.await?;
}
} else {
log::info!(
"First check for {}, status: {}",
self.name,
new_result.status
);
}
self.result = Some(new_result);
Ok(())
}
async fn handle_message(&mut self, message: SiteMessage) -> anyhow::Result<()> {
match message {
SiteMessage::Check => {
self.check().await?;
}
}
Ok(())
}
}
impl SiteResult {
fn diff(&self, rhs: &SiteResult) -> SiteResultDiff {
let status = if self.status != rhs.status {
Some(rhs.status.clone())
} else {
None
};
let bytes = self.bytes != rhs.bytes;
SiteResultDiff { status, bytes }
}
}
impl SiteResultDiff {
fn is_different(&self) -> bool {
self.status.is_some() || self.bytes
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
if std::env::var("RUST_LOG").is_err() {
std::env::set_var("RUST_LOG", "info");
}
env_logger::init();
let client = Client::builder().user_agent("Site Checker").build()?;
let mut root_handle = tokio_actors::root();
for (name, href) in SITES {
let name = name.to_string();
let href = href.to_string();
let client = client.clone();
let state = SiteState {
name,
href,
client,
result: None,
};
let handle = root_handle
.spawn_child(state, move |state, msg, _| {
Box::pin(async move { state.handle_message(msg).await })
})
.await?;
handle.every(Duration::from_secs(30 * 60), || SiteMessage::Check);
}
tokio::signal::ctrl_c().await?;
root_handle.close().await;
Ok(())
}