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
use std::env;
use std::thread;
use std::time;

use inotify::{EventMask, Events, Inotify, WatchMask};
use itertools::Itertools;

use crate::error::ErrorHandleable;

pub struct Detector {
    inotify: Inotify,
    buffer: [u8; 4096],
}

impl Default for Detector {
    fn default() -> Self {
        let mut inotify = Inotify::init().handle_error("Failed to initialize inotify");
        let current_dir = env::current_dir().handle_error("Failed to determine current directory");
        inotify
            .add_watch(current_dir, WatchMask::MODIFY | WatchMask::CREATE)
            .handle_error("Failed to add inotify watch");
        Self {
            inotify,
            buffer: [0u8; 4096],
        }
    }
}

impl Detector {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn wait_file_names(&mut self) -> Vec<String> {
        let mut file_names: Vec<String> = Self::wait_events(&mut self.inotify, &mut self.buffer)
            .filter_map(|e| Self::extract_file_name(&e))
            .collect();
        if file_names.is_empty() {
            self.wait_file_names()
        } else {
            thread::sleep(time::Duration::from_millis(200));
            file_names.extend(
                Self::get_events(&mut self.inotify, &mut self.buffer)
                    .filter_map(|e| Self::extract_file_name(&e)),
            );
            file_names.into_iter().unique().collect()
        }
    }

    fn file_changed(event: &inotify::Event<&std::ffi::OsStr>) -> bool {
        (event.mask.contains(EventMask::CREATE) || event.mask.contains(EventMask::MODIFY))
            && !event.mask.contains(EventMask::ISDIR)
    }

    fn extract_file_name(event: &inotify::Event<&std::ffi::OsStr>) -> Option<String> {
        if Self::file_changed(&event) {
            event.name.map(|name| String::from(name.to_string_lossy()))
        } else {
            None
        }
    }

    fn wait_events<'a>(inotify: &'a mut Inotify, buffer: &'a mut [u8]) -> Events<'a> {
        inotify
            .read_events_blocking(buffer)
            .handle_error("Failed to read inotify events")
    }

    fn get_events<'a>(inotify: &'a mut Inotify, buffer: &'a mut [u8]) -> Events<'a> {
        inotify
            .read_events(buffer)
            .handle_error("Failed to read inotify events")
    }
}

impl IntoIterator for Detector {
    type Item = String;
    type IntoIter = FileGen;

    fn into_iter(self) -> Self::IntoIter {
        FileGen::new(self)
    }
}

pub struct FileGen {
    detector: Detector,
    file_names: Vec<String>,
}

impl FileGen {
    pub fn new(detector: Detector) -> Self {
        Self {
            detector,
            file_names: vec![],
        }
    }
}

impl Iterator for FileGen {
    type Item = String;

    fn next(&mut self) -> Option<Self::Item> {
        if self.file_names.is_empty() {
            self.file_names = self.detector.wait_file_names();
        }
        self.file_names.pop()
    }
}