svc.fnl (3884B)
1 (local inotify (require :inotify)) 2 (local { : file-exists? : dirname : append-path } (require :anoia)) 3 (local { : file-type : dir : mktree &as fs } (require :anoia.fs)) 4 (local { : readlink } (require :lualinux)) 5 6 (fn read-line [name] 7 (with-open [f (assert (io.open name :r) (.. "can't open file " name))] 8 (f:read "*l"))) 9 10 11 ;; If the directory is missing, we cannot add a inotify watch 12 ;; for it. We need a watch that opens the parent if the pathname is missing, 13 ;; and it needs to be the resolved path not the syntactic parent. 14 15 ;; If the directory is not missing, then having a watch on the 16 ;; parent may result in extra wakeups but should only affect 17 ;; efficiency not correctness 18 19 ;; Each time the directory may have been added we need to update 20 ;; watches. If it's been removed, the watch will be removed 21 ;; automatically. inotify_add_watch when it already exists will modify 22 ;; instead of making a new one, so we can treat it as idempotent 23 24 25 (fn resolve-link [pathname] 26 (if (= (file-type pathname) :link) 27 (readlink pathname) 28 pathname)) 29 30 (fn watch-fsevents [directory-name] 31 (let [handle (inotify.init) 32 parent-name (dirname (resolve-link directory-name)) 33 refresh (fn [] 34 (handle:addwatch directory-name 35 inotify.IN_CREATE 36 inotify.IN_MOVE 37 inotify.IN_DELETE 38 inotify.IN_DELETE_SELF 39 inotify.IN_MOVED_FROM 40 inotify.IN_MOVED_TO 41 inotify.IN_CLOSE_WRITE) 42 (handle:addwatch parent-name 43 inotify.IN_CREATE 44 inotify.IN_DELETE))] 45 ;; if you are using poll() to check for events on this 46 ;; watcher and on other events at the same time, be sure 47 ;; to call fileno each time around the loop instead 48 ;; of only once 49 { 50 :fileno #(do (refresh) (handle:fileno)) 51 :wait #(do (refresh) (handle:read)) 52 :close #(handle:close) 53 })) 54 55 (fn write-value [pathname value] 56 (mktree (dirname pathname)) 57 (match (type value) 58 "string" 59 (with-open [f (assert (io.open pathname :w) (.. "can't open " pathname))] 60 (f:write value)) 61 "table" (each [k v (pairs value)] 62 (write-value (append-path pathname k) v)))) 63 64 (fn read-value [pathname] 65 (case (file-type pathname) 66 nil nil 67 :directory 68 (collect [f (fs.dir pathname)] 69 (when (not (or (= f ".") (= f ".."))) 70 (values f (read-value (append-path pathname f))))) 71 :file 72 (read-line pathname) 73 :link 74 (read-line pathname) 75 unknown 76 (error (.. "can't read " pathname " of kind \"" unknown.mode "\"")))) 77 78 (fn events [self] 79 (coroutine.wrap 80 #(while true 81 (while (not (self:ready?)) (self:wait)) 82 (coroutine.yield self) 83 (self:wait)))) 84 85 (fn open [directory] 86 (let [outputs-dir (append-path directory ".outputs") 87 has-file? #(file-exists? (append-path directory $1)) 88 watcher (watch-fsevents outputs-dir) 89 properties-dir (append-path directory ".properties")] 90 { 91 :ready? (fn [self] 92 (and (has-file? ".outputs/state") 93 (not (has-file? ".outputs/.lock")))) 94 :property (fn [_ filename] 95 (read-value (append-path properties-dir filename))) 96 :output (fn [_ filename new-value] 97 (if new-value 98 (write-value (append-path outputs-dir filename) new-value) 99 (or 100 (read-value (append-path outputs-dir filename)) 101 (read-value (append-path properties-dir filename))))) 102 :wait #(watcher:wait) 103 :close #(watcher:close) 104 :fileno #(watcher:fileno) 105 : directory 106 : events 107 })) 108 109 { : open }