|
6 | 6 |
|
7 | 7 | (def clients (ref []))
|
8 | 8 |
|
9 |
| -(defn write-line [client message] |
| 9 | +(defn write-line |
| 10 | + "Write a single message to a client, returning the client." |
| 11 | + [client message] |
10 | 12 | (socket/write-line (:socket client) message)
|
11 | 13 | client)
|
12 | 14 |
|
13 | 15 | (defn error
|
| 16 | + "Write an error to a socket, returning nil." |
14 | 17 | [socket message]
|
15 | 18 | (socket/write-line socket (str "ERROR: " message)))
|
16 | 19 |
|
17 | 20 | (defn nick-exists?
|
| 21 | + "Return truthy if a nick is already in use, otherwise nil." |
18 | 22 | [nick]
|
19 | 23 | (let [nicks (map #(-> % deref :nick) @clients)]
|
20 | 24 | (some #{nick} nicks)))
|
21 | 25 |
|
22 |
| -(defn set-nick [client nick] |
| 26 | +(defn set-nick |
| 27 | + "Set the nick of an existing client. This function is transactional." |
| 28 | + [client nick] |
23 | 29 | (dosync
|
24 | 30 | (if (nick-exists? nick)
|
25 | 31 | (error (:socket @client) "nick already exists")
|
26 | 32 | (send-off client assoc :nick nick))))
|
27 | 33 |
|
28 |
| -(defn send-message [client message] |
| 34 | +(defn send-message |
| 35 | + "Send a message to all clients but the originating one." |
| 36 | + [client message] |
29 | 37 | (doseq [d-client @clients]
|
30 | 38 | (when-not (= client d-client)
|
31 | 39 | (send-off d-client write-line (str (:nick @client) ": " message)))))
|
32 | 40 |
|
33 | 41 | (defn terminate-client!
|
| 42 | + "Close the socket and remove the client from the list." |
34 | 43 | [client]
|
35 |
| - (socket/close-socket (:socket @client)) |
36 |
| - (dosync |
37 |
| - (alter clients (partial remove #{client})))) |
| 44 | + (try |
| 45 | + (socket/close-socket (:socket @client)) |
| 46 | + (dosync |
| 47 | + (alter clients (partial remove #{client}))) |
| 48 | + (catch Throwable e |
| 49 | + (println (.getMessage e))))) |
38 | 50 |
|
39 | 51 | (defn listen-client
|
| 52 | + "Listen for and dispatch incoming messages from a client." |
40 | 53 | [client]
|
41 | 54 | (let [{:keys [socket nick channels] :or {channels []}} @client]
|
42 | 55 | (loop [line (socket/read-line socket)]
|
|
49 | 62 | (when-not (.isClosed socket)
|
50 | 63 | (recur (socket/read-line socket))))))
|
51 | 64 |
|
| 65 | +(defn handle-client-error |
| 66 | + "Handle a client error." |
| 67 | + [the-agent exception] |
| 68 | + (let [s (:socket @the-agent) |
| 69 | + msg (.getMessage exception)] |
| 70 | + (when-not (.isClosed s) |
| 71 | + (error s msg)) |
| 72 | + (println msg) |
| 73 | + (terminate-client! the-agent))) |
| 74 | + |
52 | 75 | (defn new-client
|
| 76 | + "Takes a freshly opened socket connection, creates a new client and |
| 77 | + calls the dispatcher." |
53 | 78 | [s]
|
54 | 79 | (loop [line (socket/read-line s)]
|
55 | 80 | (if-let [[_ nick] (re-matches #"USER (.*)" line)]
|
56 | 81 | (if-let [client (dosync
|
57 | 82 | (when-not (nick-exists? nick)
|
58 |
| - (let [client (agent {:socket s :nick nick :channels []})] |
| 83 | + (let [client (agent {:socket s :nick nick :channels []} |
| 84 | + :error-mode :continue |
| 85 | + :error-handler handle-client-error)] |
59 | 86 | (alter clients conj client)
|
60 | 87 | client)))]
|
61 | 88 | (listen-client client)
|
|
0 commit comments