use std::{
cell::Cell,
path::{Path, PathBuf},
time::{Duration, Instant, SystemTime},
};
#[cfg(feature = "http")]
use url::Url;
#[derive(Clone, Debug)]
pub enum Wait {
Elapsed { end_instant: Instant, not: bool },
Exists { not: bool, path: PathBuf },
Update {
not: bool,
path: PathBuf,
last_update: Cell<Option<SystemTime>>,
},
UpdateSince {
not: bool,
path: PathBuf,
trigger_duration: Duration,
},
TcpHost { not: bool, host: String },
#[cfg(feature = "http")]
HttpGet { not: bool, url: String, status: u16 },
FileSize {
not: bool,
path: PathBuf,
size_bytes: Cell<Option<u64>>,
},
Custom { f: fn() -> bool, not: bool },
}
impl Wait {
pub fn new_elapsed(end_instant: Instant) -> Self {
Self::Elapsed {
end_instant,
not: false,
}
}
pub fn new_elapsed_from_duration(duration: Duration) -> Self {
Self::Elapsed {
end_instant: std::time::Instant::now() + duration,
not: false,
}
}
#[cfg(feature = "http")]
pub fn new_http_get<T>(url: T, status: u16) -> Self
where
T: Into<String>,
{
Self::HttpGet {
not: false,
url: url.into(),
status,
}
}
pub fn new_tcp_connect<T>(host: T) -> Self
where
T: Into<String>,
{
Self::TcpHost {
not: false,
host: host.into(),
}
}
pub fn new_file_exists<T>(path: T) -> Self
where
T: Into<PathBuf>,
{
Self::Exists {
not: false,
path: path.into(),
}
}
pub fn new_file_update<T>(path: T) -> Self
where
T: Into<PathBuf>,
{
Self::Update {
not: false,
path: path.into(),
last_update: Cell::new(None),
}
}
pub fn new_file_update_since<T>(path: T, trigger_duration: Duration) -> Self
where
T: Into<PathBuf>,
{
Self::UpdateSince {
not: false,
path: path.into(),
trigger_duration,
}
}
pub fn new_file_size<T>(path: T) -> Self
where
T: Into<PathBuf>,
{
Self::FileSize {
not: false,
path: path.into(),
size_bytes: Cell::new(None),
}
}
pub fn new_custom(f: fn() -> bool) -> Self {
Self::Custom { f, not: false }
}
pub fn condition_met(&self) -> bool {
match self {
Wait::Elapsed { end_instant, not } => {
if *not {
*end_instant >= Instant::now()
} else {
*end_instant < Instant::now()
}
}
Wait::Exists { not: true, path } => !Path::new(path).exists(),
Wait::Exists { not: false, path } => Path::new(path).exists(),
#[cfg(feature = "http")]
Wait::HttpGet { not, url, status } => {
let result = ureq::get(url).call();
if *not {
*status != result.status()
} else {
*status == result.status()
}
}
Wait::TcpHost { not: false, host } => std::net::TcpStream::connect(host).is_ok(),
Wait::TcpHost { not: true, host } => std::net::TcpStream::connect(host).is_err(),
Wait::Update {
not,
path,
last_update,
} => {
let current_modified = match get_modified_time(path) {
Some(systime) => systime,
None => return true, };
match last_update.get() {
Some(last_updated) => {
let is_updated = last_updated != current_modified;
if *not {
if is_updated {
last_update.set(Some(current_modified));
false
} else {
true
}
} else {
is_updated
}
}
None => {
last_update.set(Some(current_modified));
false
}
}
}
Wait::UpdateSince {
not,
path,
trigger_duration,
} => {
let last_updated = match get_modified_time(path) {
Some(up) => up,
None => return true, };
let since_last_update = match SystemTime::now().duration_since(last_updated) {
Ok(d) => d,
Err(_) => return true, };
let is_recently_updated = since_last_update < *trigger_duration;
is_recently_updated ^ not
}
Wait::FileSize {
not,
path,
size_bytes: bytes,
} => {
match (bytes.get(), get_file_size(path)) {
(_, None) => true,
(Some(prev), Some(curr)) if !*not && prev != curr => true,
(Some(prev), Some(curr)) if *not && prev == curr => true,
(_, curr) => {
bytes.set(curr);
false
}
}
}
Wait::Custom { f, not } => {
if *not {
!(f)()
} else {
(f)()
}
} }
}
pub fn wait(&self, interval: Duration) {
loop {
let start = Instant::now();
if self.condition_met() {
return;
}
let loop_time = start.elapsed();
if interval > loop_time {
std::thread::sleep(interval - loop_time);
}
}
}
}
impl std::ops::Not for Wait {
type Output = Self;
fn not(mut self) -> Self::Output {
let not = match &mut self {
Wait::Elapsed { not, .. } => not,
Wait::Exists { not, .. } => not,
Wait::HttpGet { not, .. } => not,
Wait::TcpHost { not, .. } => not,
Wait::Update { not, .. } => not,
Wait::UpdateSince { not, .. } => not,
Wait::FileSize { not, .. } => not,
Wait::Custom { not, .. } => not,
};
*not = !*not;
self
}
}
fn get_modified_time(path: &Path) -> Option<SystemTime> {
let meta = path.metadata().ok()?;
meta.modified().ok()
}
fn get_file_size(path: &Path) -> Option<u64> {
let meta = path.metadata().ok()?;
Some(meta.len())
}
pub fn parse_duration(duration: &str) -> Option<Duration> {
let mut total_delay = 0;
let mut acc = 0;
for c in duration.chars() {
match c {
'0'..='9' => {
acc *= 10;
acc += c.to_digit(10).unwrap();
}
'd' => {
total_delay += acc * 86400;
acc = 0;
}
'h' => {
total_delay += acc * 3600;
acc = 0;
}
'm' => {
total_delay += acc * 60;
acc = 0;
}
's' => {
total_delay += acc;
acc = 0;
}
_ => return None,
}
}
total_delay += acc;
let d = Duration::from_secs(total_delay as u64);
Some(d)
}
#[cfg(feature = "http")]
pub fn parse_http_get(urlarg: &str) -> (u16, String) {
let urlbytes = urlarg.chars().collect::<Vec<_>>();
let (status_code, urlarg) = if urlarg.len() > 4
&& urlbytes[0..3].iter().all(|c| c.is_numeric())
&& urlbytes[3] == ','
{
let code = 100 * (urlbytes[0] as u16 - '0' as u16)
+ 10 * (urlbytes[1] as u16 - '0' as u16)
+ (urlbytes[2] as u16 - '0' as u16);
(code, &urlarg[4..])
} else {
(200, urlarg)
};
if let Some(url) = parse_url(urlarg) {
(status_code, url.to_string())
} else {
(status_code, urlarg.to_string())
}
}
#[cfg(feature = "http")]
fn parse_url(urlarg: &str) -> Option<Url> {
let violations = std::cell::RefCell::new(Vec::new());
let url = Url::options()
.syntax_violation_callback(Some(&|v| {
violations.borrow_mut().push(v);
}))
.parse(urlarg)
.ok()?;
Some(url)
}
pub fn validate_tcp(hostarg: &str) -> bool {
let last_colon = hostarg.char_indices().filter(|(_i, c)| c == &':').last();
if let Some((i, _c)) = last_colon {
let port = &hostarg[i + 1..];
port.parse::<u16>().is_ok()
} else {
false
}
}
mod tests {
#[test]
fn valid_tcp() {
assert!(super::validate_tcp("localhost:80"));
assert!(!super::validate_tcp("localhost"));
assert!(super::validate_tcp("127.0.0.1:80"));
assert!(!super::validate_tcp("127.0.0.1"));
assert!(super::validate_tcp("127.0.0.1:8000"));
assert!(super::validate_tcp("127.0.0.1:65534"));
assert!(super::validate_tcp("127.0.0.1:65535"));
assert!(!super::validate_tcp("127.0.0.1:65536"));
assert!(!super::validate_tcp("127.0.0.1:-1"));
}
}