thirdshift 0.15.0

Turn a GitHub issue into a ready-for-review pull request, unattended
//! Dependencies that can change a live observation without a record write.
use std::collections::BTreeSet;
use std::path::PathBuf;
use std::time::Duration;

use anyhow::Result;
use chrono::{DateTime, Local};

use super::super::reading::{Command, Entry, Snapshot};
use crate::config::UserConfig;

pub(crate) use super::super::reading::Writer as Process;

#[derive(Default, Debug)]
pub(crate) struct Watching {
    pub processes: Vec<Process>,
    pub paths: BTreeSet<PathBuf>,
    pub deadline: Option<Duration>,
}

pub(crate) fn watching(config: &UserConfig) -> Result<Watching> {
    let now = Local::now();
    let snapshot = Snapshot::load_with_silence(&config.logs_dir, now, config.silent_interval)?;
    let mut watching = Watching::default();
    let kept = crate::logs::github::load(&config.logs_dir);
    watching.deadline = kept.next_warning_change();
    if let Some(at) = snapshot.next_inbox_change(config.inbox_days, &kept) {
        deadline(at, now, &mut watching);
    }
    for entry in snapshot.entries() {
        if let Entry::Record(command) = entry {
            collect(command, None, now, &mut watching);
        }
    }
    Ok(watching)
}

fn collect(
    command: &Command,
    outer_log: Option<&std::path::Path>,
    now: DateTime<Local>,
    watching: &mut Watching,
) {
    let log = outer_log.unwrap_or(&command.command_log);
    let process = Process::capture(&command.record);
    if process.live() {
        watching.processes.push(process);
        let session = command
            .record
            .session_log
            .as_deref()
            .map(crate::logs::session::path);
        watching.paths.insert(log.to_path_buf());
        watching.paths.extend(session.iter().cloned());
    }
    if command.state == "live"
        && let Some(at) = command.next_health_change
    {
        deadline(at, now, watching);
    }
    for child in command.children.iter().chain(&command.dispatched) {
        collect(child, Some(log), now, watching);
    }
}

fn deadline(at: DateTime<Local>, now: DateTime<Local>, watching: &mut Watching) {
    if let Ok(delay) = (at - now).to_std()
        && !delay.is_zero()
    {
        watching.deadline = Some(watching.deadline.map_or(delay, |old| old.min(delay)));
    }
}