use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use crate::runtime::Command;
use crate::storage::{FolderChange, FolderChangeKind, FolderChanges, FolderWatch};
use super::state::{FileManagerMsg, FileManagerState, Wrap};
#[derive(Debug, Default)]
pub(super) enum Live {
#[default]
Off,
Unavailable,
On(Watching),
}
#[derive(Debug)]
pub(super) struct Watching {
watch: FolderWatch,
run: u64,
folders: BTreeMap<String, Option<PathBuf>>,
}
impl Watching {
fn key_of(&self, folder: &Path) -> Option<String> {
self.folders.iter().find(|(_, path)| path.as_deref() == Some(folder)).map(|(key, _)| key.clone())
}
#[cfg(test)]
pub(super) fn watched(&self) -> Vec<&str> {
self.folders.iter().filter(|(_, path)| path.is_some()).map(|(key, _)| key.as_str()).collect()
}
#[cfg(test)]
pub(super) fn run(&self) -> u64 {
self.run
}
#[cfg(test)]
pub(super) fn changes(&self) -> FolderChanges {
self.watch.changes()
}
}
pub(super) fn follow<Msg: Clone + Send + 'static>(state: &mut FileManagerState, wrap: &Wrap<Msg>) -> Command<Msg> {
if !state.follows_changes() {
return Command::none();
}
let mut commands = Vec::new();
if matches!(state.live, Live::Off) {
match FolderWatch::new() {
Ok(watch) => {
state.runs += 1;
commands.push(wait(state.runs, watch.changes(), state.patience, wrap));
state.live = Live::On(Watching { watch, run: state.runs, folders: BTreeMap::new() });
}
Err(_) => state.live = Live::Unavailable,
}
}
let visible = state.visible_folders();
let Live::On(watching) = &mut state.live else { return Command::batch(commands) };
let left: Vec<String> = watching.folders.keys().filter(|key| !visible.contains(key)).cloned().collect();
for key in left {
if let Some(Some(path)) = watching.folders.remove(&key) {
watching.watch.unwatch(&path);
}
}
let mut again = Vec::new();
let mut started = Vec::new();
for key in visible {
if watching.folders.contains_key(&key) {
continue;
}
started.push(key);
}
for key in started {
let path = state.path(&key);
let known = state.children(&key).is_some() && !state.is_loading(&key);
let Live::On(watching) = &mut state.live else { break };
let watched = watching.watch.watch(&path).is_ok();
if watched && known {
again.push(key.clone());
}
watching.folders.insert(key, watched.then_some(path));
}
commands.push(state.reread(again, wrap));
Command::batch(commands)
}
pub(super) fn changed<Msg: Clone + Send + 'static>(
state: &mut FileManagerState,
run: u64,
batch: Vec<FolderChange>,
wrap: &Wrap<Msg>,
) -> Command<Msg> {
let Live::On(watching) = &mut state.live else { return Command::none() };
if watching.run != run || batch.is_empty() {
return Command::none();
}
let next = wait(run, watching.watch.changes(), state.patience, wrap);
let mut again = Vec::new();
let mut everything = false;
for change in batch {
let Some(key) = watching.key_of(&change.folder) else { continue };
match change.kind {
FolderChangeKind::Modified => {}
FolderChangeKind::Created | FolderChangeKind::Removed | FolderChangeKind::Renamed { .. } => {
again.push(key);
}
FolderChangeKind::Gone => {
watching.folders.remove(&key);
everything = true;
}
FolderChangeKind::Overflow => everything = true,
}
}
let reread = if everything {
state.refresh(wrap)
} else {
again.sort();
again.dedup();
state.reread(again, wrap)
};
Command::batch([reread, next])
}
fn wait<Msg: Clone + Send + 'static>(
run: u64,
changes: FolderChanges,
patience: Option<std::time::Duration>,
wrap: &Wrap<Msg>,
) -> Command<Msg> {
let wrap = std::sync::Arc::clone(wrap);
Command::perform(move || match patience {
None => wrap(FileManagerMsg::Changed(run, changes.next())),
Some(bound) => match changes.next_within(bound) {
Some(batch) => wrap(FileManagerMsg::Changed(run, batch)),
None => wrap(FileManagerMsg::Quiet(run)),
},
})
}
pub(super) fn quiet<Msg: Clone + Send + 'static>(
state: &mut FileManagerState,
run: u64,
wrap: &Wrap<Msg>,
) -> Command<Msg> {
let patience = state.patience;
match &state.live {
Live::On(watching) if watching.run == run => wait(run, watching.watch.changes(), patience, wrap),
_ => Command::none(),
}
}