rootftp 0.3.0

A simple FTP server tool that allows you to play with files. Multiple users can share their files with each other. You can also build custom plugins see examples for more info.
Documentation
use crate::plugin_handler::loader::PluginInstance;
use inotify::{EventMask, Inotify, WatchMask};
use std::sync::Arc;
use std::time::Duration;
use std::{collections::HashMap, fs, path::PathBuf, thread};

pub fn start_watchers(
    ftpd_root: PathBuf,
    user: String,
    plugin_map: HashMap<String, Arc<PluginInstance>>,
) {
    let plugins: Vec<(String, Arc<PluginInstance>)> = plugin_map.into_iter().collect();

    thread::spawn(move || {
        let mut inotify = match Inotify::init() {
            Ok(i) => i,
            Err(e) => {
                eprintln!("Failed to init inotify for plugin watcher: {}", e);
                return;
            }
        };
        let mut wd_map: HashMap<_, (String, String)> = HashMap::new();
        let mut buf = [0u8; 4096];

        for (plugin_name, _) in &plugins {
            let dir = ftpd_root
                .join(&user)
                .join("plugins")
                .join(plugin_name)
                .join("input");
            let out_dir = ftpd_root
                .join(&user)
                .join("plugins")
                .join(plugin_name)
                .join("output");

            fs::create_dir_all(&dir).ok();
            fs::create_dir_all(&out_dir).ok();
            match inotify.watches().add(
                &dir,
                WatchMask::CLOSE_WRITE | WatchMask::MOVED_TO,
            ) {
                Ok(wd) => {
                    wd_map.insert(wd.clone(), (user.clone(), plugin_name.clone()));
                }
                Err(e) => {
                    eprintln!("Failed to watch plugin dir {:?}: {}", dir, e);
                }
            }
        }

        loop {
            let events = match inotify.read_events_blocking(&mut buf) {
                Ok(ev) => ev,
                Err(e) => {
                    eprintln!("inotify read_events failed: {}", e);
                    thread::sleep(Duration::from_secs(1));
                    continue;
                }
            };

            for ev in events {
                if ev.mask.contains(EventMask::ISDIR) {
                    continue;
                }
                if !ev
                    .mask
                    .intersects(EventMask::CLOSE_WRITE | EventMask::MOVED_TO)
                {
                    continue;
                }
                let Some(name) = ev.name else { continue };
                let Some((user, plugin_name)) = wd_map.get(&ev.wd) else { continue };
                let filename = name.to_string_lossy();
                let path = ftpd_root
                    .join(user)
                    .join("plugins")
                    .join(plugin_name)
                    .join("input")
                    .join(&*filename);
                let out_path = ftpd_root
                    .join(user)
                    .join("plugins")
                    .join(plugin_name)
                    .join("output");
                let Some(instance) = plugins
                    .iter()
                    .find(|(n, _)| n == plugin_name)
                    .map(|(_, p)| p)
                else { continue };
                let Some(ext) = path.extension().and_then(|s| s.to_str()) else { continue };
                if !instance.exts.contains(&ext.to_string()) {
                    continue;
                }
                let instance = instance.clone();
                let path = path.clone();
                let out_path = out_path.clone();
                thread::spawn(move || {
                    instance.plugin.on_create(&path, &out_path);
                });
            }
        }
    });
}