summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xcompojure-3.2/src/compojure/http/middleware.clj2
-rw-r--r--[-rwxr-xr-x]lib/compojure-3.2v1.jarbin401005 -> 401074 bytes
-rwxr-xr-xsrc/cookie_login.clj76
-rwxr-xr-xsrc/site.clj81
-rwxr-xr-xstatic/js/pichat.js61
-rw-r--r--static/tests/scrolling.html128
-rw-r--r--static/webcam/webcam.js16
7 files changed, 253 insertions, 111 deletions
diff --git a/compojure-3.2/src/compojure/http/middleware.clj b/compojure-3.2/src/compojure/http/middleware.clj
index f9a2dab..7c68bab 100755
--- a/compojure-3.2/src/compojure/http/middleware.clj
+++ b/compojure-3.2/src/compojure/http/middleware.clj
@@ -114,7 +114,7 @@
(let [default (or (:default options) "text/html")]
(if-let [ext (extension (:uri request))]
(let [mimetypes (or (:mimetypes options) default-mimetypes)]
- (get mimetypes ext default))
+ (get mimetypes (.toLowerCase ext) default))
default)))
(defn with-mimetypes
diff --git a/lib/compojure-3.2v1.jar b/lib/compojure-3.2v1.jar
index c41f5e9..bc19fe3 100755..100644
--- a/lib/compojure-3.2v1.jar
+++ b/lib/compojure-3.2v1.jar
Binary files differ
diff --git a/src/cookie_login.clj b/src/cookie_login.clj
index 9e501f4..e507876 100755
--- a/src/cookie_login.clj
+++ b/src/cookie_login.clj
@@ -1,23 +1,28 @@
(ns cookie-login
(:use compojure))
-(defn clear-login-token [token-key]
+(def *login-token-key* :login-token)
+(def *login-token-expiry* (* 1000 60 60 24 7)) ; one week
+
+(defn clear-login-token
"Creates an expiration cookie for a given cookie name."
+ [token-key]
(set-cookie token-key "dummy"
:expires "Thu, 01-Jan-1970 00:00:01 GMT"))
+
(defn handle-request-with-login-token
"Validates login token, handles request, and updates cookies and session
- repository. If token is invalid or an exception is raised while reading it,
- the token cookie is expired."
- [handler request expiry token-key token-maker token-reader]
- (if-let [session-info (token-reader (get-in request [:cookies token-key]))]
+ repository. If the token is invalid, the token cookie is expired."
+ [handler request token-maker token-reader login-token-key login-token-expiry]
+ (if-let [session-info (token-reader (get-in request
+ [:cookies login-token-key]))]
(let [response (handler (merge-with merge
request
{:session session-info}))
; Session variable priority:
; 1) variables set by handler
- ; 2) session variables from token-reader
+ ; 2) variables from token-reader
; 3) variables from repository
session-map (merge (request :session)
session-info
@@ -25,50 +30,45 @@
(merge-with merge
response
{:session session-map}
- (token-maker session-info expiry)))
+ (token-maker session-info)))
(merge (handler request)
- (clear-login-token token-key))))
-
-; Default expiration is a week.
-(def *default-login-token-expiry* (* 1000 60 60 24 7))
-(def *default-login-token-key* :login-token)
+ (clear-login-token login-token-key))))
(defn with-cookie-login
"Middleware to support automatic cookie login. Must be placed after
- the with-session middleware!
+ the with-session middleware.
- Accepts five configuration options:
- - token-key:
- The cookie name to store the login-token under.
- Defaults to 'login-token'.
- - expiry:
- The number of milliseconds a login token is valid for.
- Defaults to one week.
- - is-logged-in?:
- Function to apply to request's session map to determine whether to
- process login token or not. If a truthy value is returned,
+Must be given three arguments:
+ - process-login-token?
+ Function to apply to request map to determine whether to
+ process login token or not. If a false value is returned,
then the next handler is called without further processing.
- token-maker:
- Function to generate new login token from session map and
- milliseconds until login token expiry.
+ Function to generate new login token from session map.
- token-reader:
Function to generate session map from login token. Should return nil
if login token is invalid.
-"
- [handler options]
- (let [token-key (or (options :default-token-key) *default-login-token-key*)
- expiry (or (options :expiry) *default-login-token-expiry*)
- is-logged-in? (options :is-logged-in?)
- token-maker (options :token-maker)
- token-reader (options :token-reader)]
+
+ The following variables can be rebound:
+ - *login-token-key*
+ The cookie name to store the login-token under.
+ Defaults to 'login-token'.
+
+ - *login-token-expiry*
+ The number of milliseconds a login token is valid for.
+ Defaults to one week.
+"
+ [handler process-login-token? token-maker token-reader]
+ (let [login-token-key *login-token-key*
+ login-token-expiry *login-token-expiry*]
(fn [request]
- (if (or (is-logged-in? (request :session))
- (not (get-in request [:cookies token-key])))
- (handler request)
+ (if (and (get-in request [:cookies login-token-key])
+ (process-login-token? request))
(handle-request-with-login-token
handler
request
- expiry
- token-key
token-maker
- token-reader))))) \ No newline at end of file
+ token-reader
+ login-token-key
+ login-token-expiry)
+ (handler request)))))
diff --git a/src/site.clj b/src/site.clj
index b1774fd..e0a40b9 100755
--- a/src/site.clj
+++ b/src/site.clj
@@ -7,13 +7,18 @@
org.apache.commons.codec.digest.DigestUtils
javax.servlet.http.Cookie
org.antlr.stringtemplate.StringTemplateGroup)
- (:use clojure.contrib.str-utils
+ (:use clojure.xml
+ clojure.contrib.str-utils
clojure.contrib.duck-streams
clojure.contrib.sql
compojure
cookie-login
utils))
+(def *run-flusher* true)
+(def *flusher-sleep-ms* 4000)
+(def *user-timeout-ms* 15000)
+
(def template-group (new StringTemplateGroup "dumpfm" "template"))
(.setRefreshInterval template-group 3)
@@ -25,24 +30,19 @@
(System/currentTimeMillis)))
(def rooms (ref {}))
-
-(def run-flusher true)
-(def flusher-sleep-ms 4000)
-(def user-timeout-ms 15000)
-
(def flusher (agent nil))
(defn flush! [x]
- (when run-flusher
+ (when *run-flusher*
(send-off *agent* #'flush!))
(doseq [[rid room] @rooms]
(dosync
(let [users (room :users)
now (System/currentTimeMillis)
- alive? (fn [[n u]] (> (u :last-seen) (- now user-timeout-ms)))]
+ alive? (fn [[n u]] (> (u :last-seen) (- now *user-timeout-ms*)))]
(ref-set users
(into {} (filter alive? @users))))))
- (. Thread (sleep flusher-sleep-ms))
+ (. Thread (sleep *flusher-sleep-ms*))
x)
;; Configuration
@@ -59,6 +59,9 @@
;; Utils
+(defn ms-in-future [ms]
+ (+ ms (System/currentTimeMillis)))
+
(defn swap [f]
(fn [& more] (apply f (reverse more))))
@@ -141,7 +144,7 @@
"messages" (map process-message-for-json
(new-messages room since))})
-(def dumps-per-page 20)
+(def *dumps-per-page* 20)
(defn maybe-parse-int [s f]
(if s (Integer/parseInt s) f))
@@ -164,7 +167,7 @@
"WHERE room_id = ? AND m.user_id = u.user_id "
(if image-only "AND m.is_image = true " "")
"ORDER BY created_on DESC "
- "LIMIT " dumps-per-page " OFFSET ?")]
+ "LIMIT " *dumps-per-page* " OFFSET ?")]
(do-select [query room-id offset]))))
(defn count-messages-by-nick [nick image-only]
@@ -184,9 +187,18 @@
"AND r.room_id = m.room_id AND r.admin_only = false "
(if image-only "AND m.is_image = true " "")
"ORDER BY created_on DESC "
- "LIMIT " dumps-per-page " OFFSET ?")]
+ "LIMIT " *dumps-per-page* " OFFSET ?")]
(do-select [query nick offset]))))
+(defn build-room-map-from-db [room-db]
+ {:admin_only (room-db :admin_only)
+ :room_id (room-db :room_id)
+ :key (room-db :key)
+ :name (room-db :name)
+ :description (room-db :description)
+ :users (ref {})
+ :messages (ref (fetch-messages-by-room (room-db :room_id) false))})
+
;; Templates
(defn fetch-template [template-name session]
@@ -214,10 +226,10 @@
;; login-token functions
-(defn is-logged-in?
+(defn logged-in?
"Test whether user is logged in by presence of nick key in session."
- [session]
- (contains? session :nick))
+ [request]
+ (contains? (request :session) :nick))
(defn encode-login-token [nick hash expiry]
(let [token-hash (sha1-hash hash expiry)]
@@ -239,11 +251,11 @@
db-info)))))
(defn make-login-token
- [{nick :nick hash :hash} expiry]
- (let [expiration (+ (System/currentTimeMillis) expiry)]
- (set-cookie *default-login-token-key* (encode-login-token nick
- hash
- expiration)
+ [{nick :nick hash :hash}]
+ (let [expiration (ms-in-future *login-token-expiry*)]
+ (set-cookie *login-token-key* (encode-login-token nick
+ hash
+ expiration)
:expires (gmt-string (new Date expiration)))))
;; Landing
@@ -259,8 +271,8 @@
db-user (authorize-nick-hash nick hash)
remember-me (= (params :rememberme) "yes")
login-cookie (if remember-me
- (make-login-token db-user *default-login-token-expiry*)
- (clear-login-token *default-login-token-key*))]
+ (make-login-token db-user *login-token-expiry*)
+ (clear-login-token *login-token-key*))]
(if db-user
[(session-assoc-from-db db-user)
login-cookie
@@ -269,7 +281,7 @@
(defn logout [session]
[(session-dissoc :nick :user_id :is_admin :avatar)
- (clear-login-token *default-login-token-key*)
+ (clear-login-token *login-token-key*)
(redirect-to "/")])
;; Registration
@@ -298,7 +310,7 @@
is-home (and nick (= nick profile-nick))
has-avatar (non-empty-string? (user-info :avatar))
offset (maybe-parse-int offset 0)
- dump-offset (* offset dumps-per-page)
+ dump-offset (* offset *dumps-per-page*)
dumps (fetch-messages-by-nick profile-nick true dump-offset)
dump-count (count-messages-by-nick profile-nick true)
st (fetch-template "profile" session)]
@@ -310,7 +322,7 @@
(if (non-empty-string? v) (escape-html v)))))
(.setAttribute st "dumps"
(to-array (map process-message-for-output dumps)))
- (if (< (+ dump-offset dumps-per-page) dump-count)
+ (if (< (+ dump-offset *dumps-per-page*) dump-count)
(.setAttribute st "next" (inc offset)))
(if (not= offset 0)
(.setAttribute st "prev" (max (dec offset) 0)))
@@ -462,13 +474,13 @@
(defn log [session room offset params]
(let [st (fetch-template "log" session)
offset (maybe-parse-int offset 0)
- dump-offset (* offset dumps-per-page)
+ dump-offset (* offset *dumps-per-page*)
image-only (and (not (room :admin_only))
(not= (params :show) "all"))
dumps (to-array (map process-message-for-output
(fetch-messages-by-room (room :room_id) image-only dump-offset)))
dump-count (count-messages-by-room (room :room_id) image-only)]
- (if (< (+ dump-offset dumps-per-page) dump-count)
+ (if (< (+ dump-offset *dumps-per-page*) dump-count)
(.setAttribute st "next" (inc offset)))
(if (not= offset 0)
(.setAttribute st "prev" (max (dec offset) 0)))
@@ -585,15 +597,12 @@
"zip" "application/zip"})
(decorate static
- (with-mimetypes))
+ (with-mimetypes {:mimetypes mimetypes}))
(decorate pichat
- (with-cookie-login {:is-logged-in? is-logged-in?
- :token-maker make-login-token
- :token-reader read-login-token})
(with-mimetypes {:mimetypes mimetypes})
+ (with-cookie-login (comp not logged-in?) make-login-token read-login-token)
(with-session {:type :memory, :expires (* 60 60)}))
-
(decorate multipart
(with-mimetypes {:mimetypes mimetypes})
@@ -605,13 +614,7 @@
(dosync
(doseq [room-db (fetch-rooms)]
(alter rooms assoc (room-db :key)
- {:admin_only (room-db :admin_only)
- :room_id (room-db :room_id)
- :key (room-db :key)
- :name (room-db :name)
- :description (room-db :description)
- :users (ref {})
- :messages (ref (fetch-messages-by-room (room-db :room_id) false))})))
+ (build-room-map-from-db room-db))))
(run-server {:port 8080}
"/static/*" (servlet static)
diff --git a/static/js/pichat.js b/static/js/pichat.js
index 6b9c3e8..33788e2 100755
--- a/static/js/pichat.js
+++ b/static/js/pichat.js
@@ -1,24 +1,44 @@
var cache = {}
var pendingMessages = {}
+var MaxImagePosts = 40
+
function escapeHtml(txt) {
- if (!txt) { return ""; }
- else { return $("<span>").text(txt).html(); }
+ if (!txt) { return ""; }
+ else { return $("<span>").text(txt).html(); }
}
function linkify(text) {
- var URLRegex = /((\b(http\:\/\/|https\:\/\/|ftp\:\/\/)|(www\.))+(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?)/gi;
- return text.replace(URLRegex, linkReplace);
+ LastMsgContainsImage = false
+ var URLRegex = /((\b(http\:\/\/|https\:\/\/|ftp\:\/\/)|(www\.))+(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?)/gi;
+ return text.replace(URLRegex, linkReplace);
}
+// durty hack to use a global to check this... but otherwise i'd have to rewrite the String.replace function? :/
+var LastMsgContainsImage = false
function linkReplace(match){
- var PicRegex = /\.(jpg|jpeg|png|gif|bmp)$/i;
- var matchWithoutParams = match.replace(/\?.*$/i, "")
- if (PicRegex.test(matchWithoutParams)){
- return "<a target='_blank' href='" + match + "'><img src='" + match + "'></a>"
- } else {
- return "<a target='_blank' href='" + match + "'>" + match + "</a>"
- }
+ var PicRegex = /\.(jpg|jpeg|png|gif|bmp)$/i;
+ var matchWithoutParams = match.replace(/\?.*$/i, "")
+ if (PicRegex.test(matchWithoutParams)){
+ LastMsgContainsImage = true
+ return "<a target='_blank' href='" + match + "'><img src='" + match + "'></a>"
+ } else {
+ return "<a target='_blank' href='" + match + "'>" + match + "</a>"
+ }
+}
+
+var ImageMsgCount = 0
+function removeOldMessages(){
+ // don't count posts that are all text
+ if (LastMsgContainsImage) ImageMsgCount += 1;
+ while (ImageMsgCount > MaxImagePosts) {
+ var imgMsg = $(".contains-image:first")
+ if (imgMsg.length) {
+ imgMsg.prevAll().remove() // remove all text messages before the image message
+ imgMsg.remove()
+ } else break;
+ ImageMsgCount -= 1;
+ }
}
function buildMsgContent(content) {
@@ -26,13 +46,15 @@ function buildMsgContent(content) {
}
function buildMessageDiv(msg, isLoading) {
- var nick = escapeHtml(msg.nick);
- var msgId = !isLoading ? 'id="message-' + msg.msg_id + '"' : '';
- var loadingClass = isLoading ? ' loading' : '';
- return '<div class="msgDiv ' + loadingClass + '" ' + msgId + '>'
- + '<b><a href="/u/' + nick + ' ">' + nick + '</a>: </b>'
- + buildMsgContent(msg.content)
- + '</div>';
+ removeOldMessages()
+ var nick = escapeHtml(msg.nick);
+ var msgId = !isLoading ? 'id="message-' + msg.msg_id + '"' : '';
+ var loadingClass = isLoading ? ' loading' : '';
+ var containsImageClass = LastMsgContainsImage ? ' contains-image' : '';
+ return '<div class="msgDiv ' + loadingClass + containsImageClass + '" ' + msgId + '>'
+ + '<b><a href="/u/' + nick + ' ">' + nick + '</a>: </b>'
+ + buildMsgContent(msg.content)
+ + '</div>';
}
function buildUserDiv(user) {
@@ -199,7 +221,6 @@ function initChat() {
// see /static/webcam/webcam.js
if ('webcam' in window) webcam.init()
-
setTimeout(refresh, 1000);
}
@@ -279,7 +300,7 @@ function isScrolledToBottom(){
}
function scrollIfPossible(){
- if (lastScriptedScrolledPosition == messageList.scrollTop || isScrolledToBottom())
+ if (lastScriptedScrolledPosition <= messageList.scrollTop || isScrolledToBottom())
scrollToEnd()
}
diff --git a/static/tests/scrolling.html b/static/tests/scrolling.html
new file mode 100644
index 0000000..00e7b89
--- /dev/null
+++ b/static/tests/scrolling.html
@@ -0,0 +1,128 @@
+<html>
+<head>
+<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
+<script>
+
+var imageQueue = []
+
+function fetch(){
+ $.ajax({
+ "url": "http://pipes.yahoo.com/pipes/pipe.run?_id=59b7cad3b8fb595fa7cb3c2fdf0ab328&_render=json&_callback=fetched",
+ "dataType": "jsonp"
+ })
+ log("fetching more images")
+}
+
+function fetched(data){
+ log("images fetched")
+ var imageUrls = []
+ try {
+ var images = data['value']['items'][0]['recent-images']['recent-image']
+ for(var i = 0; i < images.length; i++)
+ imageQueue.push(images[i]['img'])
+ } catch(e) {
+ console.log("couldn't parse object:")
+ console.log(data)
+ }
+}
+
+function log(m){
+ $("#log").html(m)
+}
+
+function go(){
+ messagePane = $("#chat")[0]
+ maxImages = $("#max-images")[0]
+ imagePoster()
+ scrollToEnd()
+ scrollWatcher()
+}
+
+function imagePoster(){
+ if (!imageQueue.length){
+ log("queue empty")
+ } else if (Paused) {
+ log("paused")
+ } else {
+ log(imageQueue.length + " images in queue ... posting image")
+ var image = imageQueue.shift()
+ imagePost(image)
+ }
+ setTimeout(imagePoster, 500)
+}
+
+imagePosts = 0
+function imagePost(image){
+ imagePosts += 1
+ while (imagePosts > maxImages.value) {
+ var imgs = $(".image-post:first")
+ if (imgs.length) {
+ imgs.remove()
+ } else {
+ break
+ }
+ imagePosts -= 1
+ }
+ var i = $("<img/>").attr("src", image) //.load(scrollIfPossible).error(scrollIfPossible)
+ var d = $("<div class='image-post'/>").html("username: ")
+ d.append(i).appendTo("#chat")
+}
+
+var Paused = false
+function pausego(){
+ Paused = !Paused
+ $("#pausego-button").html(Paused ? "go" : "pause")
+}
+
+function isScrolledToBottom(){
+ var threshold = 50;
+
+ var containerHeight = messagePane.style.pixelHeight || messagePane.offsetHeight
+ var currentHeight = (messagePane.scrollHeight > 0) ? messagePane.scrollHeight : 0
+
+ var result = (currentHeight - messagePane.scrollTop - containerHeight < threshold);
+
+ return result;
+}
+
+function scrollIfPossible(){
+ if (lastScriptedScrolledPosition <= messagePane.scrollTop || isScrolledToBottom())
+ scrollToEnd()
+}
+
+var lastScriptedScrolledPosition = 0
+function scrollToEnd(){
+ messagePane.scrollTop = messagePane.scrollHeight
+ lastScriptedScrolledPosition = messagePane.scrollTop
+}
+
+function scrollWatcher(){
+ scrollIfPossible()
+ setTimeout(scrollWatcher, 500)
+}
+
+</script>
+<style>
+ #chat { width: 500px; height: 90%; overflow: scroll; }
+ img { max-height: 300px; }
+</style>
+</head>
+<body>
+ <div id="chat">
+ test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>
+ test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>
+ test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>
+ test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>
+ test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>
+ test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>
+ </div>
+ <p id="log"></p>
+ <button onclick="fetch()">add images</button>
+ <button onclick="pausego()" id="pausego-button">pause</button>
+ <br />
+ max image posts: <input name="max-images" id="max-images" value="50" />
+</body>
+<script>
+ go()
+</script>
+</html> \ No newline at end of file
diff --git a/static/webcam/webcam.js b/static/webcam/webcam.js
index 9829fd7..f5a5536 100644
--- a/static/webcam/webcam.js
+++ b/static/webcam/webcam.js
@@ -1,21 +1,11 @@
/* JPEGCam v1.0.8 */
/* Webcam library for capturing JPEG images and submitting to a server */
-/* Copyright (c) 2008 - 2009 Joseph Huckaby <jhuckaby@goldcartridge.com> */
+/* Copyright (c) 2008 - 2009
+ Joseph Huckaby <jhuckaby@goldcartridge.com>
+ AND TIMB, ESQ. <http://bon.gs> */
/* Licensed under the GNU Lesser Public License */
/* http://www.gnu.org/licenses/lgpl.html */
-/* Usage:
- <script language="JavaScript">
- document.write( webcam.get_html(320, 240) );
- webcam.set_api_url( 'test.php' );
- webcam.set_hook( 'onComplete', 'my_callback_function' );
- function my_callback_function(response) {
- alert("Success! PHP returned: " + response);
- }
- </script>
- <a href="javascript:void(webcam.snap())">Take Snapshot</a>
-*/
-
// Everything is under a 'webcam' Namespace
window.webcam = {
version: '1.0.8',