Skip to content

Commit dc4b2a6

Browse files
committed
Merge pull request #1 from j0ni/agents
Agents
2 parents 8aeea27 + 05a423d commit dc4b2a6

File tree

2 files changed

+84
-28
lines changed

2 files changed

+84
-28
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
target/
2+
.nrepl-port

src/chat_server/core.clj

Lines changed: 82 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,95 @@
11
(ns chat-server.core
22
(:require [clj-sockets.core :as socket]
3-
[chat-server.repl :as repl])
3+
[chat-server.repl :as repl]
4+
[clojure.string :as string])
45
(:gen-class))
56

6-
(def clients (ref {}))
7+
(def clients (ref []))
78

9+
(defn write-line
10+
"Write a single message to a client, returning the client."
11+
[client message]
12+
(socket/write-line (:socket client) message)
13+
client)
814

9-
(defn serve-client
10-
[nick client]
11-
(doseq [line (socket/read-lines client)]
12-
(println "got message from user:" line)
13-
(let [other-clients (vals (dissoc @clients nick))]
14-
(case
15-
(first (clojure.string/split line #" "))
16-
;; Instructor Note: explain why a doall is needed here
17-
"MSG" (doall (map #(socket/write-line % (str nick ": " (subs line 4))) other-clients))
15+
(defn error
16+
"Write an error to a socket, returning nil."
17+
[socket message]
18+
(socket/write-line socket (str "ERROR: " message)))
1819

19-
;;TODO: Process other kinds of command here
20+
(defn nick-exists?
21+
"Return truthy if a nick is already in use, otherwise nil."
22+
[nick]
23+
(let [nicks (map #(-> % deref :nick) @clients)]
24+
(some #{nick} nicks)))
2025

21-
;; else
22-
(socket/write-line client "ERROR: I don't understand")))))
26+
(defn set-nick
27+
"Set the nick of an existing client. This function is transactional."
28+
[client nick]
29+
(dosync
30+
(if (nick-exists? nick)
31+
(error (:socket @client) "nick already exists")
32+
(send-off client assoc :nick nick))))
2333

24-
(defn new-client
34+
(defn send-message
35+
"Send a message to all clients but the originating one."
36+
[client message]
37+
(doseq [d-client @clients]
38+
(when-not (= client d-client)
39+
(send-off d-client write-line (str (:nick @client) ": " message)))))
40+
41+
(defn terminate-client!
42+
"Close the socket and remove the client from the list."
43+
[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)))))
50+
51+
(defn listen-client
52+
"Listen for and dispatch incoming messages from a client."
2553
[client]
26-
(let [command (socket/read-line client)]
27-
(println "got message:" command)
28-
(if-let [[_ nick] (re-matches #"USER (.*)" command)]
29-
;; Instructor note: explain why we have to use a transaction here to make sure checking if user exists and adding them happens atomically
30-
(if (dosync
31-
(when-not (get @clients nick)
32-
(alter clients assoc nick client)))
33-
34-
(serve-client nick client)
35-
36-
(do
37-
(socket/write-line client "ERROR: Nick already taken")
38-
(socket/close-socket client))))))
54+
(let [{:keys [socket nick channels] :or {channels []}} @client]
55+
(loop [line (socket/read-line socket)]
56+
(let [[command & words] (string/split line #" ")]
57+
(case command
58+
"USER" (set-nick client (string/join "-" words))
59+
"MSG" (send-message client (string/join " " words))
60+
"QUIT" (terminate-client! client)
61+
(error socket "I don't understand")))
62+
(when-not (.isClosed socket)
63+
(recur (socket/read-line socket))))))
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+
75+
(defn new-client
76+
"Takes a freshly opened socket connection, creates a new client and
77+
calls the dispatcher."
78+
[s]
79+
(loop [line (socket/read-line s)]
80+
(if-let [[_ nick] (re-matches #"USER (.*)" line)]
81+
(if-let [client (dosync
82+
(when-not (nick-exists? nick)
83+
(let [client (agent {:socket s :nick nick :channels []}
84+
:error-mode :continue
85+
:error-handler handle-client-error)]
86+
(alter clients conj client)
87+
client)))]
88+
(listen-client client)
89+
(do (error s "nick is already taken, try another")
90+
(recur (socket/read-line s))))
91+
(do (error s "first set a nick with USER")
92+
(recur (socket/read-line s))))))
3993

4094
(defn -main
4195
"The hello world of chat servers"

0 commit comments

Comments
 (0)