use std::hash::BuildHasher;
use std::mem::{swap, take};
use std::sync::{Condvar, Mutex, MutexGuard};
use std::time::Duration;
use bitflags::bitflags;
use hashbrown::hash_table::Entry;
use hashbrown::{DefaultHashBuilder, HashTable};
use crate::path::CanonicalPathBuf;
bitflags! {
#[derive(Clone, Copy, Debug)]
pub struct Flags: u32 {
const NEEDS_RECURSIVE_CRAWL = 1;
const NEEDS_NON_RECURSIVE_CRAWL = 2;
const MARK_RECURSIVE = 4;
const ORIGIN_WATCHER = 8;
}
}
#[derive(Debug, Default)]
pub struct PendingChangesLock {
inner: Mutex<PendingChanges>,
condvar: Condvar,
}
impl PendingChangesLock {
pub fn take_timeout(
&self,
dst: &mut PendingChanges,
timeout: Duration,
exit: impl Fn() -> bool,
) -> bool {
let mut guard = self.inner.lock().unwrap();
let res;
(guard, res) = self
.condvar
.wait_timeout_while(guard, timeout, |changes| changes.is_empty() && !exit())
.unwrap();
if res.timed_out() {
return true;
}
swap(&mut *guard, dst);
false
}
pub fn take(&self, dst: &mut PendingChanges, exit: impl Fn() -> bool) {
let mut guard = self.inner.lock().unwrap();
guard = self
.condvar
.wait_while(guard, |changes| changes.is_empty() && !exit())
.unwrap();
swap(&mut *guard, dst);
}
pub fn lock(&self) -> MutexGuard<'_, PendingChanges> {
self.inner.lock().unwrap()
}
pub fn notify(&self) {
self.condvar.notify_all();
}
}
#[derive(Clone, Debug)]
pub struct PendingChange {
pub path: CanonicalPathBuf,
pub flags: Flags,
}
impl PendingChange {
fn consolidate(&mut self, mut new: Flags) {
new.remove(Flags::ORIGIN_WATCHER);
self.flags.insert(new);
}
}
#[derive(Default, Clone)]
pub struct PendingChanges {
path_set: HashTable<u32>,
state: DefaultHashBuilder,
changes: Vec<PendingChange>,
recrawl: bool,
}
impl std::fmt::Debug for PendingChanges {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PendingChanges")
.field("changes", &self.changes)
.field("recrawl", &self.recrawl)
.finish()
}
}
impl PendingChanges {
pub fn is_empty(&self) -> bool {
self.changes.is_empty() & !self.recrawl
}
pub fn recrawl(&mut self) {
self.path_set.clear();
self.changes.clear();
self.recrawl = true;
}
fn add(&mut self, change: PendingChange) {
if self.recrawl {
return;
}
let hash = self.state.hash_one(&change.path);
let ent = self.path_set.entry(
hash,
|&i| self.changes[i as usize].path == change.path,
|&i| self.state.hash_one(&self.changes[i as usize].path),
);
match ent {
Entry::Occupied(entry) => {
self.changes[*entry.get() as usize].consolidate(change.flags);
}
Entry::Vacant(entry) => {
entry.insert(self.changes.len() as u32);
self.changes.push(change);
}
}
}
pub fn add_watcher(
&mut self,
path: CanonicalPathBuf,
flags: Flags,
) {
self.add(PendingChange {
path,
flags: flags | Flags::ORIGIN_WATCHER,
});
}
pub fn take_recrawl(&mut self) -> bool {
take(&mut self.recrawl)
}
pub fn drain(&mut self) -> impl Iterator<Item = PendingChange> + '_ {
self.path_set.clear();
self.changes
.sort_unstable_by(|change1, change2| change1.path.cmp(&change2.path));
self.changes.drain(..)
}
}