use super::Observed;
use crate::index;
use crate::status::Change;
use crate::status::types::value;
#[derive(Clone, Copy)]
pub(super) struct Slots {
pub(super) entry: Option<usize>,
pub(super) directory: Option<usize>,
}
pub(super) struct Tracked<'a> {
pub(super) entries: &'a [index::Entry],
pub(super) directories: Vec<&'a [u8]>,
child_counts: Vec<usize>,
}
impl<'a> Tracked<'a> {
pub(super) fn new(
entries: &'a [index::Entry],
directories: Vec<&'a [u8]>,
count_children: bool,
) -> Self {
let mut tracked = Self {
entries,
directories,
child_counts: Vec::new(),
};
if count_children {
let mut counts = vec![0; tracked.directories.len() + 1];
let mut previous: Option<&[u8]> = None;
for entry in entries {
if previous != Some(&entry.path[..]) {
counts[tracked.parent_slot(&entry.path)] += 1;
}
previous = Some(&entry.path);
}
for directory in &tracked.directories {
counts[tracked.parent_slot(directory)] += 1;
}
tracked.child_counts = counts;
}
tracked
}
fn parent_slot(&self, path: &[u8]) -> usize {
path.iter()
.rposition(|byte| *byte == b'/')
.and_then(|end| self.directory(&path[..end]))
.map_or(0, |directory| directory + 1)
}
pub(super) fn child_count(&self, directory: &[u8]) -> usize {
let slot = if directory.is_empty() {
Some(0)
} else {
self.directory(directory).map(|position| position + 1)
};
slot.and_then(|slot| self.child_counts.get(slot).copied())
.unwrap_or(0)
}
pub(super) fn children(&self, directory: &[u8]) -> Vec<&'a [u8]> {
let mut prefix = directory.to_vec();
if !prefix.is_empty() {
prefix.push(b'/');
}
let mut names: Vec<&'a [u8]> = Vec::new();
let mut position = self
.entries
.partition_point(|entry| entry.path.as_slice() < prefix.as_slice());
while let Some(entry) = self.entries.get(position) {
let Some(rest) = entry.path.strip_prefix(prefix.as_slice()) else {
break;
};
let name = rest.split(|byte| *byte == b'/').next().unwrap_or(rest);
if names.last() != Some(&name) {
names.push(name);
}
let mut next = prefix.clone();
next.extend_from_slice(name);
if name.len() < rest.len() {
next.push(b'0');
position = self
.entries
.partition_point(|entry| entry.path.as_slice() < next.as_slice());
} else {
position += 1;
}
}
names
}
fn stages(&self, path: &[u8]) -> std::ops::Range<usize> {
let start = self
.entries
.partition_point(|entry| entry.path.as_slice() < path);
let count = self.entries[start..]
.iter()
.take_while(|entry| entry.path == path)
.count();
start..start + count
}
pub(super) fn entry(&self, path: &[u8]) -> Option<(usize, &index::Entry)> {
let stages = self.stages(path);
let last = stages.end.checked_sub(1).filter(|_| !stages.is_empty())?;
Some((stages.start, &self.entries[last]))
}
pub(super) fn directory(&self, path: &[u8]) -> Option<usize> {
self.directories
.binary_search_by(|directory| (*directory).cmp(path))
.ok()
}
pub(super) fn slots(&self, path: &[u8]) -> Slots {
let stages = self.stages(path);
Slots {
entry: (!stages.is_empty()).then_some(stages.start),
directory: self
.directory(path)
.map(|directory| self.entries.len() + directory),
}
}
pub(super) fn collect(&self, observations: Vec<(usize, Observed)>) -> Vec<Option<Observed>> {
let mut slots = vec![None; self.entries.len() + self.directories.len()];
for (slot, observed) in observations {
slots[slot] = Some(observed);
}
slots
}
pub(super) fn directory_states(&self, observed: &[Option<Observed>]) -> Vec<Option<Change>> {
let mut states: Vec<Option<Change>> = Vec::with_capacity(self.directories.len());
for (position, directory) in self.directories.iter().enumerate() {
let parent = directory
.iter()
.rposition(|byte| *byte == b'/')
.and_then(|end| self.directory(&directory[..end]))
.and_then(|parent| states[parent].clone());
let state = parent.or_else(|| match observed[self.entries.len() + position] {
Some(Observed::Directory) => None,
Some(_) => Some(Change::Obstructed {
path: directory.to_vec(),
}),
None => Some(Change::Deleted),
});
states.push(state);
}
states
}
pub(super) fn difference(
&self,
position: usize,
observed: &[Option<Observed>],
directories: &[Option<Change>],
) -> Option<Change> {
let entry = &self.entries[position];
if let Some(end) = entry.path.iter().rposition(|byte| *byte == b'/') {
let parent = self.directory(&entry.path[..end])?;
if let Some(change) = &directories[parent] {
return Some(change.clone());
}
}
match observed[position] {
Some(Observed::Leaf(actual)) if actual == value(entry) => None,
Some(Observed::Leaf(actual) | Observed::Resized(actual)) => {
Some(Change::Modified(actual))
}
Some(_) => Some(Change::Obstructed {
path: entry.path.clone(),
}),
None => Some(Change::Deleted),
}
}
}