Skip to content

Instantly share code, notes, and snippets.

@yogthos
Created August 17, 2014 16:06
Show Gist options
  • Save yogthos/911e6aba9802ceacd83c to your computer and use it in GitHub Desktop.
Save yogthos/911e6aba9802ceacd83c to your computer and use it in GitHub Desktop.
(ns file-watcher
(:require [clojure.set :refer [rename-keys]]
[clojure.java.io :refer [file]])
(:import
[java.nio.file
FileSystems
Path
Paths
StandardWatchEventKinds]))
(defn register-events! [dir watch-service opts]
(.register dir watch-service
(-> opts
(select-keys [StandardWatchEventKinds/ENTRY_CREATE
StandardWatchEventKinds/ENTRY_MODIFY
StandardWatchEventKinds/ENTRY_DELETE
StandardWatchEventKinds/OVERFLOW])
(keys)
(into-array))))
(defn rename-event-keys [opts]
(rename-keys opts
{:create StandardWatchEventKinds/ENTRY_CREATE
:modify StandardWatchEventKinds/ENTRY_MODIFY
:delete StandardWatchEventKinds/ENTRY_DELETE
:overflow StandardWatchEventKinds/OVERFLOW}))
(defn watch-loop [watch-service opts]
(loop []
(let [k (.take watch-service)]
(doseq [event (.pollEvents k)]
(if-let [handler (get opts (.kind event))]
(handler event)))
(when (.reset k) (recur)))))
(defn watch [path opts]
(let [dir (-> path (file) (.toURI) (Paths/get))
opts (rename-event-keys opts)]
(with-open [watch-service (.newWatchService (FileSystems/getDefault))]
(register-events! dir watch-service opts)
(watch-loop watch-service opts))))
@slipset
Copy link

slipset commented Aug 17, 2014

Here is a untested implementation with core.async

(defn register-events! [dir watch-service]
  (.register dir watch-service [StandardWatchEventKinds/ENTRY_CREATE
                                StandardWatchEventKinds/ENTRY_MODIFY
                                StandardWatchEventKinds/ENTRY_DELETE
                                StandardWatchEventKinds/OVERFLOW])

(def event-mapping {StandardWatchEventKinds/ENTRY_CREATE :create
     StandardWatchEventKinds/ENTRY_MODIFY :modify
     StandardWatchEventKinds/ENTRY_DELETE :delete
     StandardWatchEventKinds/OVERFLOW :overflow})


(defn watch-loop [watch-service c]
  (loop []
    (let [k (.take watch-service)]      
      (doseq [event (.pollEvents k)]
        (>! c (get event-mapping event)
      (when (.reset k) (recur)))))))


(defn watch [path]
  (let [dir (-> path (file) (.toURI) (Paths/get))
        opts (rename-event-keys opts)
        c (async/chan) ]
    (with-open [watch-service (.newWatchService (FileSystems/getDefault))]
      (register-events! dir watch-service)
      (watch-loop watch-service c))
    c))


(def channel (watch "/foo/bar/baz"))

(go (while true
      (let [e (<! channel)]
        (println "got event " e))))

@yogthos
Copy link
Author

yogthos commented Aug 26, 2014

neat :)

@yogthos
Copy link
Author

yogthos commented Aug 26, 2014

@slipset had to make a few small changes to get it to work. You have to run watch in a thread and use >!! in the watch-loop. Turns out you can also set a sensitivity modifier for improved polling on OS X.

(ns watcher
  (:require [clojure.set :refer [rename-keys]]
            [clojure.java.io :refer [file]]
            [clojure.core.async
             :as async
             :refer [go thread close! <! >!!]])
  (:import
       [java.nio.file
        FileSystems
        Path
        Paths
        StandardWatchEventKinds]))

(defn register-events! [dir watch-service]
  (.register dir
             watch-service
             (into-array
               [StandardWatchEventKinds/ENTRY_CREATE
                StandardWatchEventKinds/ENTRY_MODIFY
                StandardWatchEventKinds/ENTRY_DELETE
                StandardWatchEventKinds/OVERFLOW])
             (into-array [(com.sun.nio.file.SensitivityWatchEventModifier/HIGH)])))

(defn watch-loop [watch-service c run?]
  (while @run?      
      (when-let [k (.take watch-service)]        
        (doseq [event (.pollEvents k)]
          (>!! c event))
        (when (.reset k)))))

(defn watch [path run?]
  (let [dir  (-> path (file) (.toURI) (Paths/get))
        c    (async/chan)]
    (thread
      (with-open [watch-service (.newWatchService (FileSystems/getDefault))]
       (register-events! dir watch-service)
       (watch-loop watch-service c run?)))
    c))

(defn start-watch! [path]
  (let [run? (atom true)
        c (watch "resources" run?)]
    (println "started watching")
    (go
     (while @run?
      (let [e (<! c)]
        (println "got event" e))))
    (fn []
      (reset! run? false)
      (close! c))))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment