aboutsummaryrefslogtreecommitdiff
path: root/src/watcher.rs
blob: 78d74f2f2fd975076bb65ee0b12273a782f8abd9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
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::{
    components::DirworldEntity,
    resources::{DirworldCache, DirworldCodecs, DirworldObservers, DirworldRootDir},
};

#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
pub struct DirworldWatcherSet;

/// Event fired when a file watcher event is caught.
#[derive(Event, Debug)]
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>,
    root_dir: Res<DirworldRootDir>,
    mut commands: Commands,
) {
    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() {
            commands.trigger(DirworldWatcherEvent(event));
        }
    }
}