use std::path::Path;
use super::{Observation, Visibility};
use crate::error::Result;
use crate::host::Hosting;
use crate::policy::{self, RulesSource};
use crate::registry::{Identity, Registry};
use crate::rules::RulesFile;
use crate::store;
pub fn declared(
file: &RulesFile,
source: &RulesSource,
key: &str,
checkout: &Path,
) -> Option<Visibility> {
let normalized = store::normalize(key);
policy::resolve(file, source, &normalized, checkout)
.visibility
.map(Visibility::from)
}
pub fn recorded(identity: &Identity, declared: Option<Visibility>) -> Visibility {
declared.unwrap_or(identity.visibility)
}
pub fn checkout_of<'r>(registry: &'r Registry, key: &str) -> Option<&'r Path> {
registry
.checkouts
.values()
.find(|checkout| checkout.identity == key)
.map(|checkout| checkout.path.as_path())
}
pub fn refresh_named(hosting: &dyn Hosting, repo: &str) -> Result<Visibility> {
let registry = store::load()?;
let resolution = store::resolve(®istry, repo)?;
refresh(hosting, &resolution.key)
}
pub fn refresh(hosting: &dyn Hosting, key: &str) -> Result<Visibility> {
Ok(refreshed(hosting, key)?.0)
}
pub fn screens_writes(hosting: &dyn Hosting, key: &str) -> Result<bool> {
let (visibility, answered) = refreshed(hosting, key)?;
let hosted = store::normalize(key).hosted.is_some();
Ok(visibility.effective() == Visibility::Public || (hosted && !answered))
}
pub fn screens_writes_unasked(key: &str) -> Result<bool> {
let registry = store::load()?;
let (file, source) = policy::load(®istry)?;
let checkout = checkout_of(®istry, key)
.map(Path::to_path_buf)
.unwrap_or_default();
if let Some(declared) = declared(&file, &source, key, &checkout) {
return Ok(declared.effective() == Visibility::Public);
}
let hosted = store::normalize(key).hosted.is_some();
Ok(match registry.identities.get(key) {
Some(identity)
if !identity.observation.is_unknown() && !identity.visibility.is_unknown() =>
{
identity.visibility.effective() == Visibility::Public
}
_ => hosted,
})
}
fn refreshed(hosting: &dyn Hosting, key: &str) -> Result<(Visibility, bool)> {
let registry = store::load()?;
let (file, source) = policy::load(®istry)?;
let checkout = checkout_of(®istry, key)
.map(Path::to_path_buf)
.unwrap_or_default();
if let Some(declared) = declared(&file, &source, key, &checkout) {
record(key, declared, Observation::Override)?;
return Ok((declared, true));
}
let Some(slug) = crate::gh::slug(key) else {
let recorded = registry
.identities
.get(key)
.map(|identity| identity.visibility)
.unwrap_or_default();
return Ok((recorded, false));
};
let answered = hosting.for_repo(&slug).and_then(|host| host.visibility());
match answered {
Ok(visibility) if !visibility.is_unknown() => {
record(key, visibility, Observation::Host)?;
Ok((visibility, true))
}
_ => {
let unchanged = registry.identities.get(key).is_some_and(|identity| {
identity.visibility.is_unknown() && identity.observation.is_unknown()
});
if !unchanged {
record(key, Visibility::Unknown, Observation::Unknown)?;
}
Ok((Visibility::Unknown, false))
}
}
}
fn record(key: &str, visibility: Visibility, observation: Observation) -> Result<()> {
if !store::load()?.identities.contains_key(key) {
return Ok(());
}
store::update(|registry| {
if let Some(identity) = registry.identities.get_mut(key) {
identity.visibility = visibility;
identity.observation = observation;
identity.observed_at = match observation {
Observation::Unknown => None,
Observation::Host | Observation::Override => Some(crate::ids::timestamp()),
};
}
Ok(())
})
}