summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsostler <sbostler@gmail.com>2009-11-08 23:45:24 -0500
committersostler <sbostler@gmail.com>2009-11-08 23:45:24 -0500
commita87e6d26c5a4925ade9f24ae1741abd7c3d3f351 (patch)
tree6fd08c98ad57c8401a4e94ae3d44bcf12afff0d6
parent41627d28af030fa4ba37cc2ada5b06a213fffba1 (diff)
Initial Checkin
-rwxr-xr-xbin/repl.bat3
-rwxr-xr-xbin/run3
-rwxr-xr-xbin/run.bat3
-rwxr-xr-xbin/run.sh1
-rwxr-xr-x[-rw-r--r--]lib/clojure-contrib.jarbin3328562 -> 2945084 bytes
-rwxr-xr-x[-rw-r--r--]lib/clojure.jarbin1780580 -> 1534952 bytes
-rwxr-xr-xlib/commons-codec-1.3.jarbin0 -> 46725 bytes
-rwxr-xr-xlib/commons-fileupload-1.2.1.jarbin0 -> 57779 bytes
-rwxr-xr-xlib/commons-io-1.4.jarbin0 -> 109043 bytes
-rwxr-xr-xlib/compojure.jarbin0 -> 400825 bytes
-rwxr-xr-xlib/compojureold.jarbin0 -> 453316 bytes
-rwxr-xr-xlib/jetty-6.1.14.jarbin0 -> 516429 bytes
-rwxr-xr-xlib/jetty-util-6.1.14.jarbin0 -> 163122 bytes
-rwxr-xr-xlib/jline-0.9.94.jarbin0 -> 87325 bytes
-rwxr-xr-xlib/servlet-api-2.5-6.1.14.jarbin0 -> 132367 bytes
-rwxr-xr-xsrc/site.clj71
-rwxr-xr-xstatic/index.html23
-rwxr-xr-xstatic/pichat.css53
-rwxr-xr-xstatic/pichat.js170
-rwxr-xr-xstatic/reset.css54
-rwxr-xr-xstatic/spinner.gifbin0 -> 1849 bytes
-rwxr-xr-xstatic/test.html75
22 files changed, 456 insertions, 0 deletions
diff --git a/bin/repl.bat b/bin/repl.bat
new file mode 100755
index 0000000..25bb737
--- /dev/null
+++ b/bin/repl.bat
@@ -0,0 +1,3 @@
+REM Windows REPL script
+
+java -cp lib/commons-io-1.4.jar;lib/commons-fileupload-1.2.1.jar;lib/commons-codec-1.3.jar;lib/clojure.jar;lib/clojure-contrib.jar;lib/compojure.jar;lib/jetty-6.1.14.jar;lib/jetty-util-6.1.14.jar;lib/servlet-api-2.5-6.1.14.jar;lib/jline-0.9.94.jar jline.ConsoleRunner clojure.lang.Repl %1 \ No newline at end of file
diff --git a/bin/run b/bin/run
new file mode 100755
index 0000000..c2af849
--- /dev/null
+++ b/bin/run
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+java -cp .:lib/commons-io-1.4.jar:lib/commons-fileupload-1.2.1.jar:lib/commons-codec-1.3.jar:lib/jline-0.9.94.jar:lib/clojure.jar:lib/clojure-contrib.jar:lib/compojure.jar:lib/jetty-6.1.14.jar:lib/jetty-util-6.1.14.jar:lib/servlet-api-2.5-6.1.14.jar:classes jline.ConsoleRunner clojure.lang.Script \ No newline at end of file
diff --git a/bin/run.bat b/bin/run.bat
new file mode 100755
index 0000000..bec30c8
--- /dev/null
+++ b/bin/run.bat
@@ -0,0 +1,3 @@
+REM Windows runner script
+
+java -cp lib/commons-io-1.4.jar;lib/commons-fileupload-1.2.1.jar;lib/commons-codec-1.3.jar;lib/clojure.jar;lib/clojure-contrib.jar;lib/compojure.jar;lib/jetty-6.1.14.jar;lib/jetty-util-6.1.14.jar;lib/servlet-api-2.5-6.1.14.jar clojure.lang.Script %1 \ No newline at end of file
diff --git a/bin/run.sh b/bin/run.sh
new file mode 100755
index 0000000..34998e0
--- /dev/null
+++ b/bin/run.sh
@@ -0,0 +1 @@
+java -cp lib/*; clojure.lang.Repl src/site.clj \ No newline at end of file
diff --git a/lib/clojure-contrib.jar b/lib/clojure-contrib.jar
index 7b58ad2..aca1fec 100644..100755
--- a/lib/clojure-contrib.jar
+++ b/lib/clojure-contrib.jar
Binary files differ
diff --git a/lib/clojure.jar b/lib/clojure.jar
index 75ced67..fce575b 100644..100755
--- a/lib/clojure.jar
+++ b/lib/clojure.jar
Binary files differ
diff --git a/lib/commons-codec-1.3.jar b/lib/commons-codec-1.3.jar
new file mode 100755
index 0000000..957b675
--- /dev/null
+++ b/lib/commons-codec-1.3.jar
Binary files differ
diff --git a/lib/commons-fileupload-1.2.1.jar b/lib/commons-fileupload-1.2.1.jar
new file mode 100755
index 0000000..aa209b3
--- /dev/null
+++ b/lib/commons-fileupload-1.2.1.jar
Binary files differ
diff --git a/lib/commons-io-1.4.jar b/lib/commons-io-1.4.jar
new file mode 100755
index 0000000..133dc6c
--- /dev/null
+++ b/lib/commons-io-1.4.jar
Binary files differ
diff --git a/lib/compojure.jar b/lib/compojure.jar
new file mode 100755
index 0000000..29759c0
--- /dev/null
+++ b/lib/compojure.jar
Binary files differ
diff --git a/lib/compojureold.jar b/lib/compojureold.jar
new file mode 100755
index 0000000..8d3dd11
--- /dev/null
+++ b/lib/compojureold.jar
Binary files differ
diff --git a/lib/jetty-6.1.14.jar b/lib/jetty-6.1.14.jar
new file mode 100755
index 0000000..3e67d1e
--- /dev/null
+++ b/lib/jetty-6.1.14.jar
Binary files differ
diff --git a/lib/jetty-util-6.1.14.jar b/lib/jetty-util-6.1.14.jar
new file mode 100755
index 0000000..7acc988
--- /dev/null
+++ b/lib/jetty-util-6.1.14.jar
Binary files differ
diff --git a/lib/jline-0.9.94.jar b/lib/jline-0.9.94.jar
new file mode 100755
index 0000000..dafca7c
--- /dev/null
+++ b/lib/jline-0.9.94.jar
Binary files differ
diff --git a/lib/servlet-api-2.5-6.1.14.jar b/lib/servlet-api-2.5-6.1.14.jar
new file mode 100755
index 0000000..fbb5a04
--- /dev/null
+++ b/lib/servlet-api-2.5-6.1.14.jar
Binary files differ
diff --git a/src/site.clj b/src/site.clj
new file mode 100755
index 0000000..33b1bb3
--- /dev/null
+++ b/src/site.clj
@@ -0,0 +1,71 @@
+; site.clj
+
+(ns pichat
+ (:import java.lang.System)
+ (:use compojure
+ clojure.contrib.json.write))
+
+(defstruct user-struct :nick :last-seen)
+(defstruct message-struct :nick :content :timestamp)
+
+(def users (ref {}))
+(def messages (ref []))
+
+(defn resp-error [message]
+ {:status 400 :headers {} :body message})
+
+(defn resp-success [message]
+ {:status 200 :headers {} :body (json-str message)})
+
+(defn join-success [nick]
+ (alter users assoc nick (struct user-struct nick (System/currentTimeMillis)))
+ (let [users (keys @users)
+ messages (take 20 @messages)
+ data {"users" users "messages" messages}]
+ [(session-assoc :nick nick)
+ (resp-success data)]))
+
+(defn try-join [params]
+ (let [nick (params :nick)]
+ (dosync
+ (if (contains? @users nick)
+ (resp-error "NICK_TAKEN")
+ (join-success nick)))))
+
+(defn new-messages [since]
+ (reverse (take-while (fn [m] (> (m :timestamp) since)) @messages)))
+
+(defn refresh [nick]
+ (dosync
+ (let [last-seen (get-in @users [nick :last-seen])]
+ (alter users assoc-in [nick :last-seen] (System/currentTimeMillis))
+ (resp-success {"messages" (new-messages last-seen)}))))
+
+(defn swap [f]
+ (fn [& more] (apply f (reverse more))))
+
+(defn msg [session params]
+ (dosync
+ (let [nick (session :nick)
+ content (params :content)
+ msg (struct message-struct nick content (System/currentTimeMillis))]
+ (if (contains? @users nick)
+ (do (alter messages (swap cons) msg)
+ (resp-success "OK"))
+ (resp-error "UNKNOWN_USER")))))
+
+(defroutes pichat
+ (GET "/" (serve-file "static" "index.html"))
+ (GET "/static/*" (or (serve-file "static" (params :*))
+ :next))
+ (GET "/join" (try-join params))
+ (GET "/refresh" (refresh (session :nick)))
+ (GET "/msg" (msg session params))
+ (ANY "*" [404 "Page not found"]))
+
+(decorate pichat
+ (with-mimetypes)
+ (with-session {:type :memory, :expires (* 60 60)}))
+
+(run-server {:port 8080}
+ "/*" (servlet pichat))
diff --git a/static/index.html b/static/index.html
new file mode 100755
index 0000000..bd75ab1
--- /dev/null
+++ b/static/index.html
@@ -0,0 +1,23 @@
+<html>
+ <head>
+ <title>Pichat</title>
+ <link rel="stylesheet" type="text/css" href="static/reset.css">
+ <link rel="stylesheet" type="text/css" href="static/pichat.css">
+ <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
+ <script type="text/javascript" src="static/pichat.js"></script>
+ <script>
+ $(document).ready(function() {
+ $('#join').click(join);
+ $('#nick').keyup(ifEnter(join));
+ });
+ </script>
+ </head>
+ <body>
+ <div id="content">
+ <h1>Welcome to Pichat!</h1>
+ <input type="text" name="nick" id="nick">
+ <input type="submit" value="Join" id="join">
+ <img id="loginspinner" src="static/spinner.gif" style="display: none" />
+ </div>
+ </body>
+</html>
diff --git a/static/pichat.css b/static/pichat.css
new file mode 100755
index 0000000..383ba7a
--- /dev/null
+++ b/static/pichat.css
@@ -0,0 +1,53 @@
+/* pichat.css */
+
+html, body {
+ padding: 1em;
+}
+
+#content, #chatbox {
+ position: relative;
+}
+
+#messagePane {
+ border: 1px solid green;
+ height: 600px;
+ padding: 5px;
+ position: absolute;
+ width: 540px;
+}
+
+#messageList {
+ height: 575px;
+ width: 540px;
+ overflow-y: auto;
+ overflow-x: hidden;
+ border-bottom: 1px solid grey;
+}
+
+#msgInputDiv {
+ height: 15px;
+}
+
+#msgInput {
+ width: 85%;
+ margin-right: 5px;
+}
+
+#msgSubmit {
+ width: 14%;
+}
+
+.msgDiv {
+ width: 520px;
+}
+
+#userList {
+ overflow: auto;
+ border: 1px solid blue;
+ height: 600px;
+ margin: 0px;
+ position: absolute;
+ padding: 5px;
+ left: 550px;
+ width: 150px;
+} \ No newline at end of file
diff --git a/static/pichat.js b/static/pichat.js
new file mode 100755
index 0000000..b12c772
--- /dev/null
+++ b/static/pichat.js
@@ -0,0 +1,170 @@
+// pichat.js
+
+var Nick = null;
+
+function handleJoinError(resp) {
+ var respText = resp.responseText ? resp.responseText.trim() : false;
+ if (respText == 'NICK_TAKEN') {
+ alert("Nick '" + Nick + "' was taken! Please choose another.");
+ } else if (respText) {
+ alert("Cannot join! (" + respText + ")");
+ } else {
+ alert("Cannot join! Please try again later.");
+ }
+}
+
+function handleMsgError(resp) {
+ var respText = resp.responseText ? resp.responseText.trim() : false;
+ if (respText == 'UNKNOWN_USER') {
+ alert("Can't send message! Please login.");
+ } else if (respText) {
+ alert("Cannot send message! (" + respText + ")");
+ } else {
+ alert("Cannot send message!");
+ }
+}
+
+function join() {
+ $('#join, #nick').attr('disabled', true);
+ $('#loginspinner').show();
+ Nick = $('#nick').val();
+
+ onSuccess = function(json) {
+ generateChatInterface(json.users, json.messages);
+ };
+
+ onError = function(resp, textStatus, errorThrown) {
+ $('#join, #nick').attr('disabled', false);
+ $('#loginspinner').hide();
+ handleJoinError(resp);
+ };
+
+ $.ajax({
+ type: 'GET',
+ timeout: 5000,
+ url: 'join',
+ data: {'nick': Nick },
+ cache: false,
+ dataType: 'json',
+ success: onSuccess,
+ error: onError
+ });
+}
+
+function buildUserDiv(user) {
+ return '<div>' + user + '</div>';
+}
+
+// http://snippets.dzone.com/posts/show/6995
+var URLRegex = /((http\:\/\/|https\:\/\/|ftp\:\/\/)|(www\.))+(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/i;
+
+function buildMessageDiv(msg) {
+ var match = URLRegex.exec(msg.content)
+ if (match) {
+ return '<div class="msgDev"><b>' + msg.nick + ': </b>'
+ + '<img height="150" width="150" src="' + match[0] + '" /></div>';
+ } else {
+ return '<div class="msgDiv"><b>' + msg.nick + ": </b>"
+ + msg.content + "</div>";
+ }
+}
+
+function buildChatInterface(users, messages) {
+ var userList = '<div id="userlist">'
+ + $.map(users, buildUserDiv).join('') + '</div>';
+ var messageList = '<div id="messagePane">'
+ + '<div id="messageList">'
+ + $.map(messages, buildMessageDiv).join('')
+ + '</div>'
+ + '<div id="msgInputDiv">'
+ + '<input type="input" id="msgInput">'
+ + '<input type="submit" value="Enter" id="msgSubmit">'
+ + '</div>'
+ + '</div>';
+ return '<h1>Pichat</h1><div id="chatbox">' + userList + messageList + '</div>';
+}
+
+function submitMessage() {
+ var content = $('#msgInput').val();
+ var msg = { 'nick': Nick, 'content': content, 'timestamp': new Date() };
+ if (content == '') { return; }
+
+ var shouldScroll = isScrolledToBottom($('#messageList')[0]);
+
+ $('#messageList').append($(buildMessageDiv(msg)));
+ $('#msgInput').val('');
+
+ if (shouldScroll) {
+ scrollToBottom($('#messageList')[0]);
+ }
+
+ var onSuccess = function() {};
+ var onError = function(resp, textStatus, errorThrown) {
+ handleMsgError(resp);
+ };
+
+ $.ajax({
+ type: 'GET',
+ timeout: 5000,
+ url: 'msg',
+ data: {'content': content },
+ cache: false,
+ dataType: 'json',
+ success: onSuccess,
+ error: onError
+ });
+}
+
+function ifEnter(fn) {
+ return function(e) {
+ if (e.keyCode == 13) { fn(); }
+ };
+}
+
+function isScrolledToBottom(div) {
+ return Math.abs(div.scrollTop - (div.scrollHeight - div.offsetHeight)) <= 3;
+}
+
+function scrollToBottom(div) {
+ div.scrollTop = div.scrollHeight;
+}
+
+function refresh() {
+ var onSuccess = function(json) {
+ if (json.messages.length == 0) {
+ return;
+ }
+
+ var shouldScroll = isScrolledToBottom($('#messageList')[0]);
+
+ // Ignore our own messages
+ var filterFunc = function(m) { return m.nick != Nick };
+ var msgStr = $.map($.grep(json.messages, filterFunc),
+ buildMessageDiv).join('');
+ $('#messageList').append(msgStr);
+
+ if (shouldScroll) {
+ scrollToBottom($('#messageList')[0]);
+ }
+ };
+
+ var onError = function(resp, textStatus, errorThrown) {};
+
+ $.ajax({
+ type: 'GET',
+ timeout: 5000,
+ url: 'refresh',
+ cache: false,
+ dataType: 'json',
+ success: onSuccess,
+ error: onError
+ });
+}
+
+function generateChatInterface(users, messages) {
+ $('#content').html(buildChatInterface(users, messages));
+ $('#msgInput').keyup(ifEnter(submitMessage));
+ $('#msgSubmit').click(submitMessage);
+ setInterval(refresh, 1000);
+}
+
diff --git a/static/reset.css b/static/reset.css
new file mode 100755
index 0000000..8767cdd
--- /dev/null
+++ b/static/reset.css
@@ -0,0 +1,54 @@
+/* reset.css
+ From http://meyerweb.com/eric/tools/css/reset/
+ v1.0 | 20080212 */
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, font, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-size: 100%;
+ vertical-align: baseline;
+ background: transparent;
+}
+body {
+ line-height: 1;
+}
+ol, ul {
+ list-style: none;
+}
+blockquote, q {
+ quotes: none;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+ content: '';
+ content: none;
+}
+
+/* remember to define focus styles! */
+:focus {
+ outline: 0;
+}
+
+/* remember to highlight inserts somehow! */
+ins {
+ text-decoration: none;
+}
+del {
+ text-decoration: line-through;
+}
+
+/* tables still need 'cellspacing="0"' in the markup */
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+} \ No newline at end of file
diff --git a/static/spinner.gif b/static/spinner.gif
new file mode 100755
index 0000000..5b33f7e
--- /dev/null
+++ b/static/spinner.gif
Binary files differ
diff --git a/static/test.html b/static/test.html
new file mode 100755
index 0000000..1c9bfbe
--- /dev/null
+++ b/static/test.html
@@ -0,0 +1,75 @@
+<html>
+ <head>
+ <style>
+ #container {
+ position: relative;
+ width: 1000px;
+ }
+ #left {
+ border: 1px solid green;
+ height: 600px;
+ left: -89px;
+ margin: 0px auto 10%;
+ margin-left: 10%;
+ padding: 20px;
+ position: absolute;
+ top: 1%;
+ width: 500px;
+ }
+
+ #right {
+ border: 1px solid blue;
+ height: 600px;
+ margin: 0px 10%;
+ margin-right: 10%;
+ padding: 20px;
+ position: absolute;
+ left: 450px;
+ top: 1%;
+ width: 150px;
+ }
+ </style>
+ </head>
+
+ <body>
+ <div id="container">
+ <div id="left">
+ I saw the best minds of my generation destroyed by
+madness, starving hysterical naked,
+dragging themselves through the negro streets at dawn
+looking for an angry fix,
+angelheaded hipsters burning for the ancient heavenly
+connection to the starry dynamo in the machin-
+ery of night,
+who poverty and tatters and hollow-eyed and high sat
+up smoking in the supernatural darkness of
+cold-water flats floating across the tops of cities
+contemplating jazz,
+who bared their brains to Heaven under the El and
+saw Mohammedan angels staggering on tene-
+ment roofs illuminated,
+who passed through universities with radiant cool eyes
+hallucinating Arkansas and Blake-light tragedy
+among the scholars of war,
+who were expelled from the academies for crazy &
+publishing obscene odes on the windows of the
+skull,
+who cowered in unshaven rooms in underwear, burn-
+ing their money in wastebaskets and listening
+to the Terror through the wall,
+who got busted in their pubic beards returning through
+Laredo with a belt of marijuana for New York,
+who ate fire in paint hotels or drank turpentine in
+Paradise Alley, death, or purgatoried their
+torsos night after night
+with dreams, with drugs, with waking nightmares, al-
+cohol and cock and endless balls,
+incomparable blind; streets of shuddering cloud and
+lightning in the mind leaping toward poles of
+ </div>
+ <div id="right">
+ </div>
+ </div>
+ </body>
+
+</html>