//! UCXL filesystem watcher. //! //! This module provides a thin wrapper around the `notify` crate to watch a //! directory (or "project") for filesystem events. When a change is detected, //! the watcher attempts to construct a corresponding `UCXLAddress` using a //! simple heuristic and logs the event. This is primarily used by CHORUS for //! reactive workflows such as automatically updating metadata when files are //! added, modified or removed. use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher}; use std::path::Path; use std::sync::mpsc::channel; use crate::UCXLAddress; use std::str::FromStr; /// Represents a watcher rooted at a specific base path. /// /// **What**: Holds the absolute path that the watcher monitors. /// /// **How**: The path is stored as a `PathBuf`. The watcher is created via the /// `new` constructor which accepts any type that can be referenced as a `Path`. /// The underlying `notify::RecommendedWatcher` is configured with the default /// `Config` and set to watch recursively. /// /// **Why**: Encapsulating the watcher logic in a dedicated struct makes it easy /// to instantiate multiple independent watchers and keeps the public API tidy. pub struct UCXLWatcher { base_path: std::path::PathBuf, } impl UCXLWatcher { /// Creates a new `UCXLWatcher` for the given path. /// /// **What**: Accepts any generic `AsRef` so callers can pass a `&str`, /// `Path`, or `PathBuf`. /// /// **How**: The provided path is converted to a `PathBuf` and stored. /// /// **Why**: Convenience constructor used throughout CHORUS when a watcher is /// needed for a project directory. pub fn new>(path: P) -> Self { Self { base_path: path.as_ref().to_path_buf(), } } /// Starts the watch loop, blocking indefinitely while handling events. /// /// **What**: Sets up a channel, creates a `RecommendedWatcher`, and begins /// watching the `base_path` recursively. For each incoming event, it /// attempts to map the filesystem path to a UCXL address and prints a log. /// /// **How**: Uses the `notify` crate's event API. The heuristic address /// format is `ucxl://system:watcher@local:filesystem/#/`. /// It parses this string with `UCXLAddress::from_str` and logs the result. /// Errors from parsing are ignored (they simply aren't printed). /// /// **Why**: Provides a simple, observable bridge between raw filesystem /// changes and the UCXL addressing scheme, allowing other components to react /// to changes using a uniform identifier. pub fn watch_loop(&self) -> Result<(), Box> { let (tx, rx) = channel(); let mut watcher = RecommendedWatcher::new(tx, Config::default())?; watcher.watch(&self.base_path, RecursiveMode::Recursive)?; println!("UCXL Watcher started on {:?}", self.base_path); for res in rx { match res { Ok(event) => { for path in event.paths { if let Some(rel_path) = path.strip_prefix(&self.base_path).ok() { let rel_str = rel_path.to_string_lossy(); // Heuristic address mapping: ucxl://system:watcher@local:filesystem/#/path let addr_str = format!( "ucxl://system:watcher@local:filesystem/#/{}", rel_str ); if let Ok(addr) = UCXLAddress::from_str(&addr_str) { println!("[UCXL EVENT] {:?} -> {}", event.kind, addr); } } } } Err(e) => println!("watch error: {:?}", e), } } Ok(()) } }