use std::net::IpAddr;
use std::sync::mpsc::{self, Receiver};
use std::thread;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PublicIpLookup {
pub url: String,
pub interval: Duration,
}
const MIN_INTERVAL: Duration = Duration::from_secs(60);
pub struct PublicIpChecker {
lookup: PublicIpLookup,
next: Instant,
running: Option<Receiver<Result<String, String>>>,
address: Option<String>,
}
impl PublicIpChecker {
pub fn new(lookup: PublicIpLookup) -> Self {
Self {
lookup,
next: Instant::now(),
running: None,
address: None,
}
}
pub fn lookup(&self) -> &PublicIpLookup {
&self.lookup
}
pub fn poll(&mut self) -> Option<&str> {
if let Some(running) = &self.running
&& let Ok(result) = running.try_recv()
{
self.running = None;
if let Ok(address) = result {
self.address = Some(address);
}
}
if self.running.is_none() && Instant::now() >= self.next {
self.next = Instant::now() + self.lookup.interval.max(MIN_INTERVAL);
let (tx, rx) = mpsc::channel();
let url = self.lookup.url.clone();
let started = thread::Builder::new()
.name("rigtop-public-ip".to_owned())
.spawn(move || {
let _ = tx.send(fetch(&url));
});
if started.is_ok() {
self.running = Some(rx);
}
}
self.address.as_deref()
}
}
fn fetch(url: &str) -> Result<String, String> {
let output = crate::update::curl()
.args(["-fsS", "--max-time", "15"])
.arg(url)
.output()
.map_err(|err| format!("cannot run curl: {err}"))?;
if !output.status.success() {
return Err(format!("curl exited with {}", output.status));
}
parse_address(&String::from_utf8_lossy(&output.stdout))
.ok_or_else(|| "no address in the answer".to_owned())
}
fn parse_address(text: &str) -> Option<String> {
let text = text.trim();
text.parse::<IpAddr>().ok().map(|addr| addr.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reads_plain_addresses_only() {
assert_eq!(
parse_address("203.0.113.5\n").as_deref(),
Some("203.0.113.5")
);
assert_eq!(
parse_address(" 2001:db8::1 ").as_deref(),
Some("2001:db8::1")
);
assert_eq!(parse_address("<html>error</html>"), None);
assert_eq!(parse_address(""), None);
}
}