-
Notifications
You must be signed in to change notification settings - Fork 6
/
refresh.clj
98 lines (85 loc) · 2.67 KB
/
refresh.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
97
98
(ns ring.middleware.refresh
(:refer-clojure :exclude [random-uuid])
(:use [compojure.core :only (routes GET)]
[watchtower.core :only (watcher rate on-change)]
ring.middleware.params)
(:require [clojure.string :as str]
[clojure.java.io :as io])
(:import [java.util Date UUID]))
(defn- get-request? [request]
(= (:request-method request) :get))
(defn- html-content? [response]
(if-let [content-type (get-in response [:headers "Content-Type"])]
(re-find #"text/html" content-type)))
(def ^:private refresh-script
(slurp (io/resource "ring/js/refresh.js")))
(defprotocol AsString
(as-str [x]))
(extend-protocol AsString
String
(as-str [s] s)
java.io.File
(as-str [f] (slurp f))
java.io.InputStream
(as-str [i] (slurp i))
clojure.lang.ISeq
(as-str [xs] (apply str xs))
nil
(as-str [_] nil))
(defn- add-script [body script]
(if-let [body-str (as-str body)]
(str/replace
body-str
#"<head\s*(?:\s[^\/>]+)?>"
#(str % "<script type=\"text/javascript\">" script "</script>"))))
(def ^:private last-modified
(atom (Date.)))
(defn- watch-dirs! [dirs]
(watcher dirs
(rate 100)
(on-change
(fn [_] (reset! last-modified (Date.))))))
(defn- random-uuid []
(str (UUID/randomUUID)))
(defn- watch-until [reference pred timeout-ms]
(let [result (promise)
watch-key (random-uuid)]
(try
(add-watch reference
watch-key
(fn [_ _ _ value]
(when (pred value)
(deliver result true))))
(or (pred @reference)
(deref result timeout-ms false))
(finally
(remove-watch reference watch-key)))))
(def ^:private source-changed-route
(GET "/__source_changed" [since]
(let [timestamp (Long. since)]
(str (watch-until
last-modified
#(> (.getTime %) timestamp)
60000)))))
(defn- wrap-with-script [handler script]
(fn [request]
(let [response (handler request)]
(if (and (get-request? request)
(html-content? response))
(-> response
(update-in [:body] add-script script)
(update-in [:headers] dissoc "Content-Length"))
response))))
(defn wrap-refresh
"Injects Javascript into HTML responses which automatically refreshes the
browser when any file in the supplied directories is modified. Only
responses from GET requests are affected. The default directories
are 'src' and 'resources'."
([handler]
(wrap-refresh handler ["src" "resources"]))
([handler dirs]
(watch-dirs! dirs)
(wrap-params
(routes
source-changed-route
(wrap-with-script handler refresh-script)))))