use super::PluginInstruction; use std::path::PathBuf; use crate::thread_bus::ThreadSenders; use std::path::Path; use std::time::Duration; use notify_debouncer_full::{ new_debouncer, notify::{EventKind, RecommendedWatcher, RecursiveMode}, DebounceEventResult, Debouncer, RecommendedCache, }; use zellij_utils::{data::Event, errors::prelude::Result}; const DEBOUNCE_DURATION_MS: u64 = 400; pub fn watch_filesystem( senders: ThreadSenders, zellij_cwd: &Path, ) -> Result> { let path_prefix_in_plugins = PathBuf::from("/host"); let current_dir = PathBuf::from(zellij_cwd); let mut debouncer = new_debouncer( Duration::from_millis(DEBOUNCE_DURATION_MS), None, move |result: DebounceEventResult| match result { Ok(events) => { let mut create_events = vec![]; let mut read_events = vec![]; let mut update_events = vec![]; let mut delete_events = vec![]; for event in events { match event.kind { EventKind::Access(_) => read_events.push(event), EventKind::Create(_) => create_events.push(event), EventKind::Modify(_) => update_events.push(event), EventKind::Remove(_) => delete_events.push(event), _ => {}, } } let create_paths: Vec = create_events .drain(..) .map(|e| { e.paths .iter() .map(|p| { let stripped_prefix_path = p.strip_prefix(¤t_dir).unwrap_or_else(|_| p); path_prefix_in_plugins.join(stripped_prefix_path) }) .collect() }) .collect(); let read_paths: Vec = read_events .drain(..) .map(|e| { e.paths .iter() .map(|p| { let stripped_prefix_path = p.strip_prefix(¤t_dir).unwrap_or_else(|_| p); path_prefix_in_plugins.join(stripped_prefix_path) }) .collect() }) .collect(); let update_paths: Vec = update_events .drain(..) .map(|e| { e.paths .iter() .map(|p| { let stripped_prefix_path = p.strip_prefix(¤t_dir).unwrap_or_else(|_| p); path_prefix_in_plugins.join(stripped_prefix_path) }) .collect() }) .collect(); let delete_paths: Vec = delete_events .drain(..) .map(|e| { e.paths .iter() .map(|p| { let stripped_prefix_path = p.strip_prefix(¤t_dir).unwrap_or_else(|_| p); path_prefix_in_plugins.join(stripped_prefix_path) }) .collect() }) .collect(); // TODO: at some point we might want to add FileMetadata to these, but right now // the API is a bit unstable, so let's not rock the boat too much by adding another // expensive syscall let mut updates = vec![]; if !read_paths.is_empty() { updates.push(( None, None, Event::FileSystemRead(read_paths.into_iter().map(|p| (p, None)).collect()), )); } if !create_paths.is_empty() { updates.push(( None, None, Event::FileSystemCreate( create_paths.into_iter().map(|p| (p, None)).collect(), ), )); } if !update_paths.is_empty() { updates.push(( None, None, Event::FileSystemUpdate( update_paths.into_iter().map(|p| (p, None)).collect(), ), )); } if !delete_paths.is_empty() { updates.push(( None, None, Event::FileSystemDelete( delete_paths.into_iter().map(|p| (p, None)).collect(), ), )); } if !updates.is_empty() { let _ = senders.send_to_plugin(PluginInstruction::Update(updates)); } }, Err(errors) => errors .iter() .for_each(|error| log::error!("watch error: {error:?}")), }, )?; debouncer.watch(zellij_cwd, RecursiveMode::Recursive)?; Ok(debouncer) }