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
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
use std::{
    sync::{mpsc, Arc, Mutex},
    time::Duration,
    path::Path,
    ops::Deref,
    thread,
};

use log::{warn, trace};
use notify::{
    self,
    DebouncedEvent,
    RecommendedWatcher,
    RecursiveMode,
    Watcher,
};

use crate::{
    imfs::Imfs,
    rbx_session::RbxSession,
};

const WATCH_TIMEOUT: Duration = Duration::from_millis(100);

/// Watches for changes on the filesystem and links together the in-memory
/// filesystem and in-memory Roblox tree.
pub struct FsWatcher {
    watcher: RecommendedWatcher,
}

impl FsWatcher {
    /// Start a new FS watcher, watching all of the roots currently attached to
    /// the given Imfs.
    ///
    /// `rbx_session` is optional to make testing easier. If it isn't `None`,
    /// events will be passed to it after they're given to the Imfs.
    pub fn start(imfs: Arc<Mutex<Imfs>>, rbx_session: Option<Arc<Mutex<RbxSession>>>) -> FsWatcher {
        let (watch_tx, watch_rx) = mpsc::channel();

        let mut watcher = notify::watcher(watch_tx, WATCH_TIMEOUT)
            .expect("Could not create filesystem watcher");

        {
            let imfs = imfs.lock().unwrap();

            for root_path in imfs.get_roots() {
                trace!("Watching path {}", root_path.display());
                watcher.watch(root_path, RecursiveMode::Recursive)
                    .expect("Could not watch directory");
            }
        }

        {
            let imfs = Arc::clone(&imfs);
            let rbx_session = rbx_session.as_ref().map(Arc::clone);

            thread::spawn(move || {
                trace!("Watcher thread started");
                while let Ok(event) = watch_rx.recv() {
                    // handle_fs_event expects an Option<&Mutex<T>>, but we have
                    // an Option<Arc<Mutex<T>>>, so we coerce with Deref.
                    let session_ref = rbx_session.as_ref().map(Deref::deref);

                    handle_fs_event(&imfs, session_ref, event);
                }
                trace!("Watcher thread stopped");
            });
        }

        FsWatcher {
            watcher,
        }
    }

    pub fn stop_watching_path(&mut self, path: &Path) {
        match self.watcher.unwatch(path) {
            Ok(_) => {},
            Err(e) => {
                warn!("Could not unwatch path {}: {}", path.display(), e);
            },
        }
    }
}

fn handle_fs_event(imfs: &Mutex<Imfs>, rbx_session: Option<&Mutex<RbxSession>>, event: DebouncedEvent) {
    match event {
        DebouncedEvent::Create(path) => {
            trace!("Path created: {}", path.display());

            {
                let mut imfs = imfs.lock().unwrap();
                imfs.path_created(&path).unwrap();
            }

            if let Some(rbx_session) = rbx_session {
                let mut rbx_session = rbx_session.lock().unwrap();
                rbx_session.path_created(&path);
            }
        },
        DebouncedEvent::Write(path) => {
            trace!("Path created: {}", path.display());

            {
                let mut imfs = imfs.lock().unwrap();
                imfs.path_updated(&path).unwrap();
            }

            if let Some(rbx_session) = rbx_session {
                let mut rbx_session = rbx_session.lock().unwrap();
                rbx_session.path_updated(&path);
            }
        },
        DebouncedEvent::Remove(path) => {
            trace!("Path removed: {}", path.display());

            {
                let mut imfs = imfs.lock().unwrap();
                imfs.path_removed(&path).unwrap();
            }

            if let Some(rbx_session) = rbx_session {
                let mut rbx_session = rbx_session.lock().unwrap();
                rbx_session.path_removed(&path);
            }
        },
        DebouncedEvent::Rename(from_path, to_path) => {
            trace!("Path renamed: {} to {}", from_path.display(), to_path.display());

            {
                let mut imfs = imfs.lock().unwrap();
                imfs.path_moved(&from_path, &to_path).unwrap();
            }

            if let Some(rbx_session) = rbx_session {
                let mut rbx_session = rbx_session.lock().unwrap();
                rbx_session.path_renamed(&from_path, &to_path);
            }
        },
        other => {
            trace!("Unhandled FS event: {:?}", other);
        },
    }
}