Skip to content

Commit

Permalink
Merge pull request #1 from j0ni/agents
Browse files Browse the repository at this point in the history
Agents
  • Loading branch information
j0ni committed Mar 23, 2015
2 parents 8aeea27 + 05a423d commit dc4b2a6
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 28 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
target/
.nrepl-port
110 changes: 82 additions & 28 deletions src/chat_server/core.clj
Original file line number Diff line number Diff line change
@@ -1,41 +1,95 @@
(ns chat-server.core
(:require [clj-sockets.core :as socket]
[chat-server.repl :as repl])
[chat-server.repl :as repl]
[clojure.string :as string])
(:gen-class))

(def clients (ref {}))
(def clients (ref []))

(defn write-line
"Write a single message to a client, returning the client."
[client message]
(socket/write-line (:socket client) message)
client)

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

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

;; else
(socket/write-line client "ERROR: I don't understand")))))
(defn set-nick
"Set the nick of an existing client. This function is transactional."
[client nick]
(dosync
(if (nick-exists? nick)
(error (:socket @client) "nick already exists")
(send-off client assoc :nick nick))))

(defn new-client
(defn send-message
"Send a message to all clients but the originating one."
[client message]
(doseq [d-client @clients]
(when-not (= client d-client)
(send-off d-client write-line (str (:nick @client) ": " message)))))

(defn terminate-client!
"Close the socket and remove the client from the list."
[client]
(try
(socket/close-socket (:socket @client))
(dosync
(alter clients (partial remove #{client})))
(catch Throwable e
(println (.getMessage e)))))

(defn listen-client
"Listen for and dispatch incoming messages from a client."
[client]
(let [command (socket/read-line client)]
(println "got message:" command)
(if-let [[_ nick] (re-matches #"USER (.*)" command)]
;; Instructor note: explain why we have to use a transaction here to make sure checking if user exists and adding them happens atomically
(if (dosync
(when-not (get @clients nick)
(alter clients assoc nick client)))

(serve-client nick client)

(do
(socket/write-line client "ERROR: Nick already taken")
(socket/close-socket client))))))
(let [{:keys [socket nick channels] :or {channels []}} @client]
(loop [line (socket/read-line socket)]
(let [[command & words] (string/split line #" ")]
(case command
"USER" (set-nick client (string/join "-" words))
"MSG" (send-message client (string/join " " words))
"QUIT" (terminate-client! client)
(error socket "I don't understand")))
(when-not (.isClosed socket)
(recur (socket/read-line socket))))))

(defn handle-client-error
"Handle a client error."
[the-agent exception]
(let [s (:socket @the-agent)
msg (.getMessage exception)]
(when-not (.isClosed s)
(error s msg))
(println msg)
(terminate-client! the-agent)))

(defn new-client
"Takes a freshly opened socket connection, creates a new client and
calls the dispatcher."
[s]
(loop [line (socket/read-line s)]
(if-let [[_ nick] (re-matches #"USER (.*)" line)]
(if-let [client (dosync
(when-not (nick-exists? nick)
(let [client (agent {:socket s :nick nick :channels []}
:error-mode :continue
:error-handler handle-client-error)]
(alter clients conj client)
client)))]
(listen-client client)
(do (error s "nick is already taken, try another")
(recur (socket/read-line s))))
(do (error s "first set a nick with USER")
(recur (socket/read-line s))))))

(defn -main
"The hello world of chat servers"
Expand Down

0 comments on commit dc4b2a6

Please sign in to comment.