use shep_client::shep_core::protocol::ProcessInfo;
use std::sync::Arc;
use crate::{
bot::{channel, monitor},
config::Config,
error::Error,
names::Names,
shepherd::Live,
stop::Stop,
stream::{self, Sink},
};
#[derive(Debug, PartialEq, Eq)]
enum OwnId<'a> {
Unadopted,
Filtered(u32),
Unresolved(&'a str),
}
fn resolve_own_id<'a>(handshake: Option<&'a str>, roll: &[ProcessInfo]) -> OwnId<'a> {
match handshake {
None => OwnId::Unadopted,
Some(name) => match roll
.iter()
.find(|info| info.name == name && info.dog.is_some())
{
Some(info) => OwnId::Filtered(info.id),
None => OwnId::Unresolved(name),
},
}
}
fn warn_once(resolved: bool, warned: &mut bool) -> bool {
if resolved {
*warned = false;
false
} else {
!core::mem::replace(warned, true)
}
}
fn unresolved_message(name: &str) -> String {
format!(
"shep-discord: {name} is adopted but its own id has not resolved yet, so this dog \
cannot yet tell its own lines apart from the rest of the bus; not streaming until it \
does"
)
}
pub async fn stream_once<S: Sink>(
live: &Live,
handshake: Option<&str>,
config: &Config,
sink: &S,
wired: Option<&Arc<monitor::watch::Wired<channel::Live>>>,
unresolved_warned: &mut bool,
stop: &mut Stop,
) -> Result<(), Error> {
let roll = live.flock().await.unwrap_or_default();
let mut names = Names::new();
names.refresh(&roll);
let own_id = match resolve_own_id(handshake, &roll) {
OwnId::Unadopted => None,
OwnId::Filtered(id) => {
warn_once(true, unresolved_warned);
Some(id)
}
OwnId::Unresolved(name) => {
if warn_once(false, unresolved_warned) {
eprintln!("{}", unresolved_message(name));
}
return Ok(());
}
};
stream::run(live, config, own_id, names, sink, wired, stop).await
}
#[cfg(test)]
mod tests {
use shep_client::shep_core::{protocol::DogSource, status::ProcStatus};
use super::*;
fn sheep(id: u32, name: &str) -> ProcessInfo {
ProcessInfo::builder(id, name, ProcStatus::Online).build()
}
fn dog(id: u32, name: &str) -> ProcessInfo {
ProcessInfo::builder(id, name, ProcStatus::Online)
.dog(Some(DogSource::BuiltIn))
.build()
}
#[test]
fn no_handshake_needs_no_filter() {
assert_eq!(resolve_own_id(None, &[]), OwnId::Unadopted);
}
#[test]
fn a_handshake_the_roll_knows_resolves() {
assert_eq!(
resolve_own_id(Some("shep-discord"), &[dog(9, "shep-discord")]),
OwnId::Filtered(9)
);
}
#[test]
fn a_handshake_the_roll_cannot_yet_name_stays_unresolved() {
assert_eq!(
resolve_own_id(Some("shep-discord"), &[]),
OwnId::Unresolved("shep-discord")
);
}
#[test]
fn a_dog_sharing_a_name_with_a_sheep_resolves_to_the_dog() {
assert_eq!(
resolve_own_id(
Some("shep-discord"),
&[dog(9, "shep-discord"), sheep(1, "shep-discord")]
),
OwnId::Filtered(9)
);
}
#[test]
fn a_sheep_alone_under_the_name_stays_unresolved() {
assert_eq!(
resolve_own_id(Some("shep-discord"), &[sheep(1, "shep-discord")]),
OwnId::Unresolved("shep-discord")
);
}
#[test]
fn warn_once_warns_once_per_outage_and_resets_on_success() {
let mut warned = false;
assert!(warn_once(false, &mut warned), "first outage cycle warns");
assert!(
!warn_once(false, &mut warned),
"a repeat of the same outage stays silent"
);
assert!(
!warn_once(true, &mut warned),
"a resolved call never warns, and resets the state"
);
assert!(
warn_once(false, &mut warned),
"a fresh outage after a reset warns again"
);
}
#[test]
fn nothing_printed_for_a_person_carries_a_dash() {
crate::test_support::assert_no_dashes(&unresolved_message("shep-discord"));
}
}