/
watching.clj
96 lines (83 loc) · 3.74 KB
/
watching.clj
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
(ns figwheel-sidecar.watching
(:require
[clojure.java.io :as io]
[clojure.core.async :refer [go-loop chan <! put! alts! timeout close!]]
[hawk.core :as hawk]))
(defn single-files [files]
(reduce (fn [acc f]
(update-in acc
[(.getCanonicalPath (.getParentFile f))]
conj f))
{} files))
(defn files-and-dirs [source-paths]
(group-by #(if (.isDirectory %) :dirs :files)
(->> (map io/file source-paths)
(filter #(.exists %)))))
;; relies on canonical strings
(defn is-subdirectory [dir child]
(.startsWith child (str dir java.io.File/separator)))
;;; we are going to have to throttle this
;; so that we can catch more than one file at a time
(defn take-until-timeout [in t]
(let [time-out (timeout t)]
(go-loop [collect []]
(when-let [[v ch] (alts! [in time-out])]
(if (= ch time-out)
collect
(recur (conj collect v)))))))
(defn default-hawk-options [hawk-options]
(let [hawk-options (or hawk-options {})]
(if (= (:watcher hawk-options)
:polling)
(merge {:sensitivity :high} hawk-options)
hawk-options)))
(defn watch!
([hawk-options source-paths callback wait-time-ms]
(let [hawk-options (default-hawk-options hawk-options)
wait-time-ms (or wait-time-ms 50)
throttle-chan (chan)
{:keys [files dirs]} (files-and-dirs source-paths)
individual-file-map (single-files files)
canonical-source-dirs (set (map #(.getCanonicalPath %) dirs))
source-paths (distinct
(concat (map str dirs)
(map #(.getParent %) files)))
valid-file? (fn [file]
(and file
(.isFile file)
(not (.isHidden file))
(let [file-path (.getCanonicalPath file)
n (.getName file)]
(and
(not= \. (first n))
(not= \# (first n))
(or
;; just skip this if
;; there are no individual-files
(empty? individual-file-map)
;; if file is on a path that is already being watched we are cool
(some #(is-subdirectory % file-path) canonical-source-dirs)
;; if not we need to see if its an individually watched file
(when-let [acceptable-paths
(get individual-file-map
(.getCanonicalPath (.getParentFile file)))]
(some #(= (.getCanonicalPath %) file-path) acceptable-paths)))))))
watcher (hawk/watch! hawk-options
[{:paths source-paths
:filter hawk/file?
:handler (fn [ctx e]
(put! throttle-chan e))}])]
(go-loop []
(when-let [v (<! throttle-chan)]
(let [files (<! (take-until-timeout throttle-chan wait-time-ms))]
(when-let [result-files (not-empty (distinct (filter valid-file? (map :file (cons v files)))))]
(callback result-files)))
(recur)))
{:watcher watcher
:throttle-chan throttle-chan}))
([hawk-options source-paths callback]
(watch! hawk-options source-paths callback 50)))
(defn stop! [{:keys [throttle-chan watcher]}]
(hawk/stop! watcher)
(Thread/sleep 200)
(close! throttle-chan))