diff options
author | 2024-10-11 16:03:43 -0400 | |
---|---|---|
committer | 2024-10-11 16:03:43 -0400 | |
commit | 535436e2e9164f9880e507d7057a8cdbf261608e (patch) | |
tree | db370db8e0caea99a9e6e3c501c7ea7e3b6dd708 /src/watcher.rs | |
parent | ebc3ccfea4027a3eb0c80f6a3d64b6425d32d1ef (diff) | |
parent | 16c1574e400d73198713336e18975ff37ab78290 (diff) |
Merge pull request #1 from exvacuum/navigation
Navigation
Diffstat (limited to 'src/watcher.rs')
-rw-r--r-- | src/watcher.rs | 139 |
1 files changed, 139 insertions, 0 deletions
diff --git a/src/watcher.rs b/src/watcher.rs new file mode 100644 index 0000000..b94c0d4 --- /dev/null +++ b/src/watcher.rs @@ -0,0 +1,139 @@ +use std::{path::{Path, PathBuf}, time::Duration}; + +use async_channel::{Receiver, Sender}; +use bevy::{prelude::*, tasks::IoTaskPool}; +use notify::{ + event::{AccessKind, AccessMode, DataChange, MetadataKind, ModifyKind, RenameMode}, + EventKind, RecursiveMode, Watcher, +}; +use notify_debouncer_full::{new_debouncer, DebounceEventResult}; + +use crate::{ + commands::process_entry, + components::DirworldEntity, + resources::{DirworldCodecs, DirworldObservers, DirworldRootDir}, +}; + +#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] +pub struct DirworldWatcherSet; + +/// Event fired when a file watcher event is caught. +#[derive(Event)] +pub struct DirworldWatcherEvent(pub notify::Event); + +#[derive(Resource)] +pub struct WatcherChannels { + tx_control: Sender<PathBuf>, + rx_changes: Receiver<notify::Event>, +} + +pub fn setup(mut commands: Commands) { + let (tx_control, rx_control) = async_channel::unbounded(); + let (tx_changes, rx_changes) = async_channel::unbounded(); + IoTaskPool::get() + .spawn(async move { file_watcher(rx_control, tx_changes).await }) + .detach(); + + commands.insert_resource(WatcherChannels { + tx_control, + rx_changes, + }) +} + +async fn file_watcher(rx: Receiver<PathBuf>, tx: Sender<notify::Event>) { + let (watcher_tx, watcher_rx) = std::sync::mpsc::channel(); + let mut debouncer = new_debouncer(Duration::from_millis(500), None, move |result: DebounceEventResult| { + match result { + Ok(events) => for event in events.iter() { + watcher_tx.send(event.clone()).unwrap(); + } + Err(errors) => for error in errors.iter() { + error!("{error:?}"); + } + } + }).unwrap(); + let mut old_path: Option<PathBuf> = None; + loop { + while let Ok(message) = rx.try_recv() { + if let Some(old_path) = &old_path { + debouncer.watcher().unwatch(old_path).unwrap(); + } + debouncer.watcher().watch(&message, RecursiveMode::NonRecursive).unwrap(); + old_path = Some(message); + } + + while let Ok(event) = watcher_rx.try_recv() { + tx.send(event.event.clone()).await.unwrap(); + } + } +} + +pub fn update( + watcher_channels: Res<WatcherChannels>, + mut event_writer: EventWriter<DirworldWatcherEvent>, + root_dir: Res<DirworldRootDir>, +) { + if root_dir.is_changed() { + if let Some(project_dir) = &root_dir.0 { + let _ = watcher_channels.tx_control.try_send(project_dir.clone()); + } + } else { + while let Ok(event) = watcher_channels.rx_changes.try_recv() { + event_writer.send(DirworldWatcherEvent(event)); + } + } +} + +pub fn handle_changes( + mut event_reader: EventReader<DirworldWatcherEvent>, + mut commands: Commands, + dirworld_entities: Query<(Entity, &DirworldEntity)>, + observers: Res<DirworldObservers>, + codecs: Res<DirworldCodecs>, +) { + if !event_reader.is_empty() { + for DirworldWatcherEvent(event) in event_reader.read() { + info!("Watcher Event: {event:?}"); + match event.kind { + EventKind::Remove(_) | EventKind::Modify(ModifyKind::Name(RenameMode::From)) => { + for path in &event.paths { + remove_entity(&mut commands, &dirworld_entities, path); + } + } + EventKind::Create(_) | EventKind::Modify(ModifyKind::Name(RenameMode::To)) => { + for path in &event.paths { + process_entry(&mut commands, path, &observers, &codecs); + } + } + EventKind::Modify(ModifyKind::Name(RenameMode::Both)) + => { + remove_entity(&mut commands, &dirworld_entities, &event.paths[0]); + process_entry(&mut commands, &event.paths[1], &observers, &codecs); + } + // EventKind::Modify(ModifyKind::Data(DataChange::Content)) + EventKind::Modify(ModifyKind::Metadata(MetadataKind::Any)) => { + remove_entity(&mut commands, &dirworld_entities, &event.paths[0]); + process_entry(&mut commands, &event.paths[0], &observers, &codecs); + } + _ => { + // warn!("Not Processed.") + } + } + } + } +} + +fn remove_entity( + commands: &mut Commands, + dirworld_entities: &Query<(Entity, &DirworldEntity)>, + path: &Path, +) { + if let Some((entity, _)) = dirworld_entities + .iter() + .find(|(_, dirworld_entity)| dirworld_entity.path == *path) + { + commands.entity(entity).despawn_recursive(); + } else { + warn!("Failed to find entity corresponding to path for despawning: {path:?}"); + } +} |