commit - 7c20cf6f8ff15852c42ea0a124c7072173e41c60
commit + 5bf6d120ef53d18feb4cefb6be97b47a408da166
blob - 2d2064bd5a743e522fa883ea1ed71d33bb7ca18b
blob + fc0efeb04981353d47fd06c87543b0644e2da615
--- src/main.rs
+++ src/main.rs
fn serve() {
build();
+ std::thread::spawn(|| {
+ watch_and_rebuild();
+ });
+
let addr = "127.0.0.1:8080";
let listener = TcpListener::bind(addr).expect("bind");
println!("serving at http://{}", addr);
// Filesystem
//////////////////////////////////////////////////////////////////////////////
+fn walk_dirs(dir: &Path, dirs: &mut Vec<PathBuf>) {
+ dirs.push(dir.to_path_buf());
+ let entries = match fs::read_dir(dir) {
+ Ok(e) => e,
+ Err(_) => return,
+ };
+ for entry in entries {
+ let path = match entry {
+ Ok(e) => e.path(),
+ Err(_) => continue,
+ };
+ if path.is_dir() {
+ walk_dirs(&path, dirs);
+ }
+ }
+}
+
fn walk(dir: &Path, files: &mut Vec<PathBuf>) {
let entries = match fs::read_dir(dir) {
Ok(e) => e,
html.push_str("</ul>");
html
}
+
+//////////////////////////////////////////////////////////////////////////////
+// Watcher
+//////////////////////////////////////////////////////////////////////////////
+
+fn watch_paths() -> Vec<PathBuf> {
+ let mut dirs = Vec::new();
+ for base in &["content", "templates", "static"] {
+ let path = Path::new(base);
+ if path.exists() {
+ walk_dirs(path, &mut dirs);
+ }
+ }
+ dirs
+}
+
+#[cfg(target_os = "linux")]
+fn watch_and_rebuild() {
+ use std::ffi::CString;
+
+ unsafe extern "C" {
+ fn inotify_init() -> i32;
+ fn inotify_add_watch(
+ fd: i32, path: *const i8, mask: u32,
+ ) -> i32;
+ fn read(fd: i32, buf: *mut u8, count: usize) -> isize;
+ }
+
+ const IN_MODIFY: u32 = 0x02;
+ const IN_CREATE: u32 = 0x100;
+ const IN_DELETE: u32 = 0x200;
+ const MASK: u32 = IN_MODIFY | IN_CREATE | IN_DELETE;
+
+ let fd = unsafe { inotify_init() };
+ if fd < 0 {
+ eprintln!("inotify_init failed");
+ return;
+ }
+
+ let dirs = watch_paths();
+ for dir in &dirs {
+ let cstr = CString::new(
+ dir.to_str().expect("path to str"),
+ )
+ .expect("cstring");
+ let wd = unsafe {
+ inotify_add_watch(fd, cstr.as_ptr(), MASK)
+ };
+ if wd < 0 {
+ eprintln!("watch failed: {}", dir.display());
+ } else {
+ log!("watching {}", dir.display());
+ }
+ }
+
+ log!("watcher ready");
+
+ let mut buf = [0u8; 4096];
+ loop {
+ let n = unsafe {
+ read(fd, buf.as_mut_ptr(), buf.len())
+ };
+ if n <= 0 {
+ continue;
+ }
+ log!("change detected, rebuilding");
+ build();
+ }
+}
+
+#[cfg(target_os = "openbsd")]
+fn watch_and_rebuild() {
+ use std::ffi::CString;
+
+ unsafe extern "C" {
+ fn kqueue() -> i32;
+ fn open(path: *const i8, flags: i32) -> i32;
+ fn kevent(
+ kq: i32,
+ changelist: *const KEvent,
+ nchanges: i32,
+ eventlist: *mut KEvent,
+ nevents: i32,
+ timeout: *const Timespec,
+ ) -> i32;
+ }
+
+ #[repr(C)]
+ struct KEvent {
+ ident: usize,
+ filter: i16,
+ flags: u16,
+ fflags: u32,
+ data: isize,
+ udata: *mut u8,
+ }
+
+ #[repr(C)]
+ struct Timespec {
+ tv_sec: i64,
+ tv_nsec: i64,
+ }
+
+ const EVFILT_VNODE: i16 = -4;
+ const EV_ADD: u16 = 0x0001;
+ const EV_CLEAR: u16 = 0x0020;
+ const NOTE_WRITE: u32 = 0x0002;
+ const NOTE_DELETE: u32 = 0x0001;
+ const NOTE_RENAME: u32 = 0x0020;
+ const O_RDONLY: i32 = 0;
+ const FFLAGS: u32 =
+ NOTE_WRITE | NOTE_DELETE | NOTE_RENAME;
+
+ let kq = unsafe { kqueue() };
+ if kq < 0 {
+ eprintln!("kqueue failed");
+ return;
+ }
+
+ let dirs = watch_paths();
+ let mut fds: Vec<i32> = Vec::new();
+
+ for dir in &dirs {
+ let cstr = CString::new(
+ dir.to_str().expect("path to str"),
+ )
+ .expect("cstring");
+ let fd = unsafe { open(cstr.as_ptr(), O_RDONLY) };
+ if fd < 0 {
+ eprintln!("open failed: {}", dir.display());
+ continue;
+ }
+ fds.push(fd);
+ log!("watching {}", dir.display());
+ }
+
+ let changes: Vec<KEvent> = fds
+ .iter()
+ .map(|&fd| KEvent {
+ ident: fd as usize,
+ filter: EVFILT_VNODE,
+ flags: EV_ADD | EV_CLEAR,
+ fflags: FFLAGS,
+ data: 0,
+ udata: std::ptr::null_mut(),
+ })
+ .collect();
+
+ unsafe {
+ kevent(
+ kq,
+ changes.as_ptr(),
+ changes.len() as i32,
+ std::ptr::null_mut(),
+ 0,
+ std::ptr::null(),
+ );
+ }
+
+ log!("watcher ready");
+
+ let mut event = KEvent {
+ ident: 0,
+ filter: 0,
+ flags: 0,
+ fflags: 0,
+ data: 0,
+ udata: std::ptr::null_mut(),
+ };
+
+ loop {
+ let n = unsafe {
+ kevent(
+ kq,
+ std::ptr::null(),
+ 0,
+ &mut event,
+ 1,
+ std::ptr::null(),
+ )
+ };
+ if n <= 0 {
+ continue;
+ }
+ log!("change detected, rebuilding");
+ build();
+ }
+}