holdon 0.4.0

Wait for anything. Know why if it doesn't.
Documentation
use std::path::Path;
use std::time::Instant;

use super::{AttemptCtx, Hintable, err_stage, ok_stage};
use crate::diagnostic::{Stage, StageKind};
use crate::target::FileMode;
use crate::util::format_error_chain;

pub(super) async fn probe(path: &Path, mode: FileMode, ctx: AttemptCtx) -> Vec<Stage> {
    let start = Instant::now();
    if let Some(stage) = stat_once(path, mode, start).await {
        return vec![stage];
    }
    #[cfg(feature = "notify-fs")]
    {
        if let Some(stage) = wait_for_event(path, mode, ctx, start).await {
            return stage;
        }
    }
    #[cfg(not(feature = "notify-fs"))]
    {
        let _ = ctx;
    }
    vec![not_matched(path, mode, start)]
}

async fn stat_once(path: &Path, mode: FileMode, start: Instant) -> Option<Stage> {
    match tokio::fs::symlink_metadata(path).await {
        Ok(_) => match mode {
            FileMode::Present => Some(ok_stage(StageKind::File, start.elapsed())),
            FileMode::Absent => None,
        },
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => match mode {
            FileMode::Absent => Some(ok_stage(StageKind::File, start.elapsed())),
            FileMode::Present => None,
        },
        Err(e) => Some(err_stage(
            StageKind::File,
            start.elapsed(),
            format_error_chain(&e),
            e.hint(),
        )),
    }
}

fn not_matched(path: &Path, mode: FileMode, start: Instant) -> Stage {
    let msg = match mode {
        FileMode::Present => "path does not exist",
        FileMode::Absent => "path still exists",
    };
    let _ = path;
    err_stage(StageKind::File, start.elapsed(), msg, None)
}

#[cfg(feature = "notify-fs")]
async fn wait_for_event(
    path: &Path,
    mode: FileMode,
    ctx: AttemptCtx,
    start: Instant,
) -> Option<Vec<Stage>> {
    use std::sync::Arc;

    use notify::{RecommendedWatcher, RecursiveMode, Watcher};
    use tokio::sync::Notify;
    use tokio::time::timeout;

    let parent = path.parent().filter(|p| !p.as_os_str().is_empty())?;
    let parent_is_dir = tokio::fs::metadata(parent).await.is_ok_and(|m| m.is_dir());
    if !parent_is_dir {
        return None;
    }
    let bell = Arc::new(Notify::new());
    let kicker = bell.clone();
    let mut watcher: RecommendedWatcher = match notify::recommended_watcher(move |_res| {
        kicker.notify_one();
    }) {
        Ok(w) => w,
        Err(_) => return None,
    };
    if watcher.watch(parent, RecursiveMode::NonRecursive).is_err() {
        return None;
    }

    if let Some(stage) = stat_once(path, mode, start).await {
        return Some(vec![stage]);
    }

    let deadline = ctx.attempt_timeout;
    let result = timeout(deadline, async {
        loop {
            bell.notified().await;
            if let Some(stage) = stat_once(path, mode, start).await {
                return Some(stage);
            }
        }
    })
    .await;
    drop(watcher);
    result.ok().flatten().map(|s| vec![s])
}