use super::super::drift::{self, Drift};
use super::super::{desired_watches, needs_liveness_reprobe};
use super::Deriver;
use crate::fs_watcher::RootKind;
use crate::opslog::{self, OpRow};
use crate::projects;
use crate::projects::join;
use crate::watch::Mark;
use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
const OPS_TAIL: usize = 256;
impl Deriver {
pub(super) fn reconcile(&mut self, mark: Mark) -> Vec<Drift> {
let before: BTreeSet<PathBuf> = self.workspaces.iter().map(|w| w.path.clone()).collect();
let announceable = self.announceable();
self.workspaces = crate::binding::workspaces(&self.roots.yog_data, &self.roots.lernie_data);
let desired = desired_watches(&self.roots, &self.workspaces);
crate::state::lock_watchset(&self.watches).reconcile(&desired);
let known: BTreeSet<PathBuf> = self.workspaces.iter().map(|w| w.path.clone()).collect();
if before != known {
self.changed = true;
}
self.trees.retain(|path, _| known.contains(path));
let mut missing = Vec::new();
for w in &self.workspaces {
if !self.trees.contains_key(&w.path) {
missing.push((w.path.clone(), mark));
}
}
self.schedule.mark(missing);
let cloned: Vec<PathBuf> = self.balls_by_project.keys().cloned().collect();
self.rebuild_join(&cloned);
if mark != Mark::Sweep {
return Vec::new();
}
before
.symmetric_difference(&known)
.filter(|ws| ws.parent().is_some_and(|root| announceable.contains(root)))
.cloned()
.map(Drift::Unenumerated)
.collect()
}
fn announceable(&self) -> BTreeSet<PathBuf> {
let set = crate::state::lock_watchset(&self.watches);
desired_watches(&self.roots, &[])
.into_iter()
.filter(|(root, kind)| {
matches!(kind, RootKind::NamesRoot | RootKind::WorkspacesRoot)
&& set.watches(root, *kind)
})
.map(|(root, _)| root)
.collect()
}
pub(super) fn cheap_sweep(&mut self) -> Vec<Drift> {
let found = self.reconcile(Mark::Sweep);
let mut live: Vec<(PathBuf, Vec<String>)> = Vec::new();
for (path, tree) in &self.trees {
if needs_liveness_reprobe(tree) {
let ids = tree.agents.iter().map(|a| a.agent_id.clone()).collect();
live.push((path.clone(), ids));
}
}
for (path, ids) in live {
self.probes.invalidate_liveness(&path, &ids);
self.schedule.mark([(path, Mark::Poll)]);
}
found
}
pub(super) fn full_sweep(&mut self) -> Vec<Drift> {
let found = self.reconcile(Mark::Sweep);
self.refresh_balls();
self.refresh_ops();
self.adopt_windows();
let all: Vec<(PathBuf, Mark)> = self
.workspaces
.iter()
.map(|w| (w.path.clone(), Mark::Sweep))
.collect();
self.schedule.mark(all);
found
}
pub(super) fn report_drift(&mut self, found: &[Drift]) {
if found.is_empty() {
return;
}
let root = self.roots.yog_state.clone();
let cwd = root.to_string_lossy().into_owned();
for entry in drift::entries(&self.clock.stamp(), &cwd, found) {
let _ = opslog::append(&root, &entry);
}
self.refresh_ops();
}
pub(super) fn refresh_balls(&mut self) {
let all = projects::enumerate(&self.roots.balls_clones);
self.projects = all.iter().map(|p| p.path.clone()).collect();
let visible: Vec<PathBuf> = projects::visible(&all)
.into_iter()
.map(|p| p.path.clone())
.collect();
self.balls_by_project = visible
.iter()
.filter_map(|p| Some((p.clone(), self.balls.live(p).ok()?)))
.collect();
self.rebuild_join(&visible);
}
pub(super) fn refetch_project(&mut self, project: &Path) {
let live = self.balls.live(project).unwrap_or_default();
let closed = self.balls.closed(project).unwrap_or_default();
self.balls_by_project.insert(project.to_path_buf(), live);
self.closed_by_project.insert(project.to_path_buf(), closed);
let cloned: Vec<PathBuf> = self.balls_by_project.keys().cloned().collect();
self.rebuild_join(&cloned);
self.refresh_ops();
}
fn rebuild_join(&mut self, cloned: &[PathBuf]) {
let rows = join::join(
cloned,
&self.balls_by_project,
&self.closed_by_project,
&self.workspaces,
);
if rows != self.join_rows {
self.join_rows = rows;
self.changed = true;
}
}
pub(super) fn refresh_ops(&mut self) {
let root = self.roots.yog_state.clone();
let rows: Vec<OpRow> = opslog::tail(&root, OPS_TAIL)
.iter()
.map(|entry| OpRow::from(&opslog::detached::fold(&root, entry)))
.collect();
if rows != self.ops {
self.ops = rows;
self.changed = true;
}
}
}