summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordumpfmprod <dumpfmprod@ubuntu.(none)>2010-02-24 08:11:03 -0500
committerdumpfmprod <dumpfmprod@ubuntu.(none)>2010-02-24 08:11:03 -0500
commit7b9c7f610974c56f13e05387c1f5ff9d7ca16ad0 (patch)
tree4086d35a5a4fa49808ec3a9f5fb8b0d9109ac93c
parent0f8f1b234276514560e6df3bb6f6bac56fa9807d (diff)
parent49ef1a688025d9183452fe5f1dccf7802d64dc62 (diff)
resolved merge
-rwxr-xr-xbin/repl.bat2
-rwxr-xr-xsrc/cookie_login.clj5
-rw-r--r--src/image_utils.clj3
-rwxr-xr-xsrc/site.clj209
-rwxr-xr-xstatic/js/home.js8
-rwxr-xr-xstatic/js/pichat.js51
-rwxr-xr-xtemplate/log.st7
-rwxr-xr-xtemplate/logged_dump.st1
-rwxr-xr-xtemplate/profile.st8
9 files changed, 201 insertions, 93 deletions
diff --git a/bin/repl.bat b/bin/repl.bat
index 2417315..ffa0230 100755
--- a/bin/repl.bat
+++ b/bin/repl.bat
@@ -1,3 +1,3 @@
REM Windows REPL script
-java -server -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-3.2v1.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;lib/postgresql-8.4-701.jdbc4.jar;lib/stringtemplate-3.2.1.jar;lib/antlr-2.7.7.jar;src/ jline.ConsoleRunner clojure.lang.Repl %1 \ No newline at end of file
+java -Xmx256m -server -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-3.2v1.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;lib/postgresql-8.4-701.jdbc4.jar;lib/stringtemplate-3.2.1.jar;lib/antlr-2.7.7.jar;src/ jline.ConsoleRunner clojure.lang.Repl %1 \ No newline at end of file
diff --git a/src/cookie_login.clj b/src/cookie_login.clj
index e507876..8c948a6 100755
--- a/src/cookie_login.clj
+++ b/src/cookie_login.clj
@@ -38,7 +38,7 @@
"Middleware to support automatic cookie login. Must be placed after
the with-session middleware.
-Must be given three arguments:
+ 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,
@@ -56,8 +56,7 @@ Must be given three arguments:
- *login-token-expiry*
The number of milliseconds a login token is valid for.
- Defaults to one week.
-"
+ 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*]
diff --git a/src/image_utils.clj b/src/image_utils.clj
new file mode 100644
index 0000000..638cd05
--- /dev/null
+++ b/src/image_utils.clj
@@ -0,0 +1,3 @@
+(ns image-utils
+ (:import javax.imageio.ImageIO))
+
diff --git a/src/site.clj b/src/site.clj
index fb78a9b..4db69e8 100755
--- a/src/site.clj
+++ b/src/site.clj
@@ -13,8 +13,10 @@
clojure.contrib.json.write
clojure.contrib.sql
compojure
+ utils
cookie-login
- utils))
+ session-sweeper
+ feed))
(def *run-flusher* true)
(def *flusher-sleep-ms* 4000)
@@ -33,9 +35,7 @@
(def rooms (ref {}))
(def flusher (agent nil))
-(defn flush! [x]
- (when *run-flusher*
- (send-off *agent* #'flush!))
+(defn flush-inactive-users! [x]
(doseq [[rid room] @rooms]
(dosync
(let [users (room :users)
@@ -43,9 +43,14 @@
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*)
+ (when *run-flusher*
+ (send *agent* #'flush-inactive-users!))
x)
+(defn start-user-flusher! []
+ (send flusher flush-inactive-users!))
+
;; Configuration
(def *server-url*
@@ -54,9 +59,11 @@
"http://localhost:8080"))
(def *image-directory* "images")
+(def *avatar-directory* "avatars")
-; Create image directory if it doesn't exist.
+; Create image directories if they don't exist.
(.mkdir (new File *image-directory*))
+(.mkdir (new File *avatar-directory*))
;; Utils
@@ -121,6 +128,7 @@
(defn process-message-for-output [d]
{"nick" (escape-html (d :nick))
+ "avatar" (escape-html (d :avatar))
"message_id" (d :message_id)
"created_on" (.format formatter (d :created_on))
"content" (escape-html (d :content))})
@@ -163,7 +171,7 @@
(defn fetch-messages-by-room
([room-id image-only] (fetch-messages-by-room room-id image-only 0))
([room-id image-only offset]
- (let [query (str "SELECT m.content, m.message_id, m.created_on, u.nick "
+ (let [query (str "SELECT m.content, m.message_id, m.created_on, u.nick, u.avatar "
"FROM messages m, users u "
"WHERE room_id = ? AND m.user_id = u.user_id "
(if image-only "AND m.is_image = true " "")
@@ -182,7 +190,7 @@
(defn fetch-messages-by-nick
([nick image-only] (fetch-messages-by-nick nick image-only 0))
([nick image-only offset]
- (let [query (str "SELECT m.content, m.created_on, u.nick "
+ (let [query (str "SELECT m.content, m.created_on, u.nick, u.avatar "
"FROM messages m, users u, rooms r "
"WHERE m.user_id = u.user_id AND u.nick = ? "
"AND r.room_id = m.room_id AND r.admin_only = false "
@@ -238,22 +246,25 @@
(defn parse-login-token [token]
(let [x (.split token "\\%")]
- (if (not (= (alength x) 3))
- nil)
- (try [(aget x 0) (Long/parseLong (aget x 1)) (aget x 2)]
- (catch NumberFormatException _ nil))))
-
+ (if (= (alength x) 3)
+ (try [(aget x 0) (Long/parseLong (aget x 1)) (aget x 2)]
+ (catch NumberFormatException _ nil)))))
(defn read-login-token [token]
- nil)
+ (if-let [[nick expiry token-hash] (parse-login-token token)]
+ (if (>= expiry (System/currentTimeMillis))
+ (let [db-info (fetch-nick nick)
+ computed-hash (sha1-hash (db-info :hash) expiry)]
+ (if (= token-hash computed-hash)
+ (select-keys db-info [:user_id :nick :is_admin :avatar]))))))
(defn make-login-token
[{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)))))
+ (set-cookie *login-token-key*
+ (encode-login-token nick hash expiration)
+ :expires
+ (gmt-string (new Date expiration)))))
;; Landing
@@ -301,45 +312,51 @@
;; Profile
(defn profile [session profile-nick offset]
- (let [user-info (fetch-nick profile-nick)]
- (if user-info
- (let [nick (session :nick)
- 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*)
- dumps (fetch-messages-by-nick profile-nick true dump-offset)
- dump-count (count-messages-by-nick profile-nick true)
- st (fetch-template "profile" session)]
- (do
- (.setAttribute st "is_home" is-home)
- (doseq [a [:nick :avatar :contact :bio]]
- (let [v (user-info a)]
- (.setAttribute st (name a)
- (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)
- (.setAttribute st "next" (inc offset)))
- (if (not= offset 0)
- (.setAttribute st "prev" (max (dec offset) 0)))
+ (if-let [user-info (fetch-nick profile-nick)]
+ (let [nick (session :nick)
+ 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*)
+ dumps (fetch-messages-by-nick profile-nick true dump-offset)
+ dump-count (count-messages-by-nick profile-nick true)
+ st (fetch-template "profile" session)]
+ (do
+ (.setAttribute st "is_home" is-home)
+ (doseq [a [:nick :avatar :contact :bio]]
+ (let [v (user-info a)]
+ (.setAttribute st (name a)
+ (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)
+ (.setAttribute st "next" (inc offset)))
+ (if (not= offset 0)
+ (.setAttribute st "prev" (max (dec offset) 0)))
(.toString st)))
- (resp-error "NO_USER"))))
+ (resp-error "NO_USER")))
+
+
+(defn update-user-db [user-id attr val]
+ (with-connection db
+ (update-values "users" ["user_id = ?" user-id] {attr val})))
+
+(defn download-avatar [session url]
+ (let [url false]
+ (update-user-db (session :user_id) "avatar" url)
+ (resp-success url)))
(defn update-profile [session params]
(let [user-id (session :user_id)
attr (params :attr)
val (params :val)
attr-set #{"avatar" "contact" "bio"}]
- (if (and user-id attr val
- (contains? attr-set attr))
- (do
- (with-connection db
- (update-values "users" ["user_id = ?" user-id] {attr val}))
- (if (= attr "avatar")
- [(session-assoc :avatar val) "OK"]
- "OK"))
- (resp-error "BAD_REQUEST"))))
+ (cond (not user-id) (resp-error "MUST_LOGIN")
+ (not (and user-id attr val)) (resp-error "BAD_REQUEST")
+ (not (contains? attr-set attr)) (resp-error "BAD_REQUEST")
+ (= attr "avatar") (download-avatar session val)
+ :else (do (update-user-db user-id attr val)
+ (resp-success "OK")))))
;; Chat
@@ -383,11 +400,12 @@
nick (session :nick)
users (room :users)]
(if nick
- (if (contains? @users nick)
- (alter users assoc-in [nick :last-seen] now)
- (alter (room :users) assoc nick (user-struct-from-session session))))
- (resp-success (assoc (updates room since)
- :timestamp now)))))
+ (if-let [user-info (@users nick)]
+ ; Incorporate avatar updates
+ (commute users assoc nick (merge user-info {:last-seen now
+ :avatar (session :avatar)}))
+ (commute (room :users) assoc nick (user-struct-from-session session))))
+ (resp-success (assoc (updates room since) :timestamp now)))))
(defn validated-refresh [session params]
(let [room-key (params :room)
@@ -493,32 +511,64 @@
;; Upload
+(def *avatar-dimensions* [50 50])
+
+(defn is-image-file? [path]
+ true)
+
(defn format-filename [s]
(let [spaceless (.replace s \space \-)
subbed (re-gsub #"[^\w.-]" "" spaceless)]
(str (System/currentTimeMillis) "-" subbed)))
-(defn image-url-from-file [f]
- (str-join "/" [*server-url* "images" (.getName f)]))
+(defn image-url-from-file [d f]
+ (str-join "/" [*server-url* d (.getName f)]))
+
(defn do-upload [session image room]
(let [filename (format-filename (:filename image))
dest (File. (rel-join *image-directory* filename))
- url (image-url-from-file dest)
+ url (image-url-from-file "images" dest)
msg-id (msg-db (session :user_id) (room :room_id) url)
- now (new Date)
- msg (struct message-struct (session :nick) url now msg-id)]
- (dosync
- (add-message msg room))
- (copy (:tempfile image) dest)
- [200 url]))
+ msg (struct message-struct (session :nick) url (new Date) msg-id)]
+ (do
+ (dosync
+ (add-message msg room))
+ (copy (:tempfile image) dest)
+ (resp-success url))))
(defn upload [session params]
(let [room-key (params :room)
- nick (session :nick)]
+ nick (session :nick)
+ image (params :image)]
(cond (not nick) [200 "NOT_LOGGED_IN"]
+ (not image) [200 "INVALID_REQUEST"]
+ (not (is-image-file? (image :filename))) [200 "INVALID_IMAGE"]
(not (validate-room-access room-key session)) [200 "UNKNOWN_ROOM"]
- :else (do-upload session (:image params) (@rooms room-key)))))
+ :else (do-upload session image (@rooms room-key)))))
+
+(defn copy-and-resize [image dest]
+ ; TODO: resize
+ (copy image dest))
+
+;; N.B. -- Upload responses aren't JSON-evaluated
+(defn do-upload-avatar [session image]
+ (let [filename (format-filename (:filename image))
+ dest (File. (rel-join *avatar-directory* filename))
+ url (image-url-from-file "avatars" dest)]
+ (do
+ (copy-and-resize (:tempfile image) dest)
+ (update-user-db (session :user_id) "avatar" url)
+ [(session-assoc :avatar url)
+ [200 url]])))
+
+(defn upload-avatar [session params]
+ (let [image (params :image)]
+ (cond (not image) [200 "INVALID_REQUEST"]
+ (not (session :nick)) [200 "NOT_LOGGED_IN"]
+ (not (is-image-file? (image :filename))) [200 "INVALID_IMAGE"]
+ :else (do-upload-avatar session image))))
+
;; 404
(defn unknown-page [params]
@@ -533,15 +583,15 @@
(defn serve-static [dir path]
; TODO: cache policy for other static files (js, css, etc.)
(let [cache-header (if (re-find pic-regex path)
- {:headers {"Cache-Control"
- "post-check=3600,pre-check=43200"}}
+ {:headers {"Cache-Control" "max-age=604800,public"}}
{})]
[cache-header
(serve-file dir path)]))
(defroutes static
(GET "/static/*" (serve-static "static" (params :*)))
- (GET "/images/*" (serve-static *image-directory* (params :*))))
+ (GET "/images/*" (serve-static *image-directory* (params :*)))
+ (GET "/avatars/*" (serve-static *avatar-directory* (params :*))))
(defroutes pichat
(GET "/" (no-cache (landing session)))
@@ -574,7 +624,8 @@
(ANY "*" (unknown-page params)))
(defroutes multipart
- (POST "/upload" (upload session params)))
+ (POST "/upload/message" (upload session params))
+ (POST "/upload/avatar" (upload-avatar session params)))
;; Add jpeg to list
(def mimetypes
@@ -612,11 +663,27 @@
(doseq [room-db (fetch-rooms)]
(alter rooms assoc (room-db :key)
(build-room-map-from-db room-db))))
+<<<<<<< HEAD
(run-server {:port 8080}
"/static/*" (servlet static)
"/images/*" (servlet static)
- "/upload" (servlet multipart)
+ "/avatars/*" (servlet static)
+ "/upload/*" (servlet multipart)
"/*" (servlet pichat))
(send-off flusher flush!)
+=======
+
+(defn start-server
+ ([port] (run-server {:port port}
+ "/static/*" (servlet static)
+ "/images/*" (servlet static)
+ "/upload" (servlet multipart)
+ "/*" (servlet pichat)))
+ ([] (start-server 8080)))
+
+(start-server)
+(start-user-flusher!)
+(start-session-pruner!)
+>>>>>>> 8003ba27e49a630b24090f3af01426f35e85af67
diff --git a/static/js/home.js b/static/js/home.js
index 107486c..e9be833 100755
--- a/static/js/home.js
+++ b/static/js/home.js
@@ -5,7 +5,6 @@ function ifEnter(fn) {
}
function initLoginForm() {
-
var nick = "#nickInput", nickFiller = "username"
var pass = "#passwordInput", passLabel = "#passwordInputLabel", passFiller = "password"
var submit = "#signin-submit"
@@ -145,17 +144,16 @@ function initBigHand(id){
}
function login() {
- //$('#passwordInput, #loginSubmit').blur();
var nick = $('#nickInput').val();
var password = $('#passwordInput').val();
var rememberme = $('#remembermeInput').attr('checked') ? 'yes' : '';
var hash = hex_sha1(nick + '$' + password + '$dumpfm');
var onSuccess = function(json) {
+ location.href = location.href;
if (typeof pageTracker !== 'undefined') {
- pageTracker._setCustomVar(1, "logged-in", nick, 1);
- }
- location.href = "/chat";
+ pageTracker._setCustomVar(1, "logged-in", nick);
+ }
};
var onError = function(resp, textStatus, errorThrown) {
diff --git a/static/js/pichat.js b/static/js/pichat.js
index 641662f..071e8e6 100755
--- a/static/js/pichat.js
+++ b/static/js/pichat.js
@@ -61,6 +61,7 @@ function buildMessageDiv(msg, isLoading) {
}
function buildUserDiv(user) {
+ console.warn(user);
if (user.avatar) {
return '<div class="username">'
+ '<a href="/u/' + escapeHtml(user.nick) + '" target="_blank">'
@@ -149,6 +150,14 @@ function setUserList(users) {
$("#userList").html($.map(users, buildUserDiv).join(''));
}
+function flattenUserJson(users) {
+ var s = "";
+ $.map(users.sort(), function(user) {
+ s += user.nick + user.avatar;
+ });
+ return s;
+}
+
function updateUI(msgs, users) {
if (window['growlize'] && msgs && msgs.length > 0) {
$.map(msgs, buildGrowlDataAndPopDatShit)
@@ -156,7 +165,8 @@ function updateUI(msgs, users) {
addNewMessages(msgs);
}
if (users !== null) {
- var flattened = users.sort().join(",")
+ var flattened = flattenUserJson(users);
+ console.log(flattened);
if (!('userlist' in cache) || flattened != cache.userlist) {
$("#userList").html($.map(users, buildUserDiv).join(''));
}
@@ -231,7 +241,6 @@ function initChat() {
}
function initProfile() {
-
jQuery(".linkify").each(function() {
var text = jQuery(this).text();
jQuery(this).html(linkify(text));
@@ -241,7 +250,7 @@ function initProfile() {
$('.logged-dump .content').each(function() {
var t = $(this);
t.html(buildMsgContent(t.text()));
- });
+ });
var onSubmit = function(attr, newVal, oldVal) {
newVal = $.trim(newVal);
@@ -276,6 +285,10 @@ function initProfile() {
'callbackShowErrors': false };
$('#contact.editable, #bio.editable').editInPlace(textareaOpts);
+ if ($('#upload').length > 0) {
+ setupUploadAvatar('upload');
+ }
+
};
function initLog() {
@@ -283,7 +296,6 @@ function initLog() {
var t = $(this);
t.html(buildMsgContent(t.text()));
});
-
}
// TODO
@@ -302,7 +314,7 @@ function setupUpload(elementId, roomKey) {
}
}
new AjaxUpload(elementId, {
- action: '/upload',
+ action: '/upload/message',
autoSubmit: true,
name: 'image',
data: { room: roomKey },
@@ -311,6 +323,35 @@ function setupUpload(elementId, roomKey) {
});
}
+function setupUploadAvatar(elementId) {
+ // NOTE: AjaxUpload responses aren't converted from JSON.
+ var onSubmit = function(file, error) {
+ $('#spinner').show();
+ };
+ var onComplete = function(file, resp) {
+ $('#spinner').hide();
+ if (resp == 'INVALID_REQUEST') {
+ location.href = location.href;
+ } else if (resp == 'NOT_LOGGED_IN') {
+ location.href = location.href;
+ } else if (resp == 'INVALID_IMAGE') {
+ alert("Sorry, dump.fm can't deal with your image. Pick another :(");
+ return;
+ }
+ var s = '<img id="avatarPic" src="' + resp + '" width="150" />';
+ $('#avatarPic').replaceWith(s).show();
+ $('#avatar').text(resp);
+ };
+ new AjaxUpload(elementId, {
+ action: '/upload/avatar',
+ autoSubmit: true,
+ name: 'image',
+ onSubmit: onSubmit,
+ onComplete: onComplete
+ });
+}
+
+
// scrolling stuff
// this code keeps the div scrolled to the bottom, but will also let the user scroll up, without jumping down
diff --git a/template/log.st b/template/log.st
index 425a0fb..7281e11 100755
--- a/template/log.st
+++ b/template/log.st
@@ -16,12 +16,9 @@
<div id="log">
- <div id="loghead">
-
- </div>
+ <div id="loghead"></div>
<br>
-
<div id="posts">
$if(dumps)$
$dumps: { d | $logged_dump(dump=d)$ }$
@@ -31,7 +28,7 @@
<div id="pnav">
$if(next)$
- <div id="pnavn"><a href="/$roomkey$/log/$next$">next &#9758;</a></div>
+ <div id="pnavn"><a href="/$roomkey$/log/$next$">next &#9758;</a></div>
$endif$
&nbsp;
diff --git a/template/logged_dump.st b/template/logged_dump.st
index dcda175..c7cbbd2 100755
--- a/template/logged_dump.st
+++ b/template/logged_dump.st
@@ -32,6 +32,7 @@ img{
</style>
<div class="logged-dump">
<div>$dump.created_on$ -- by <b><a href="/u/$dump.nick$">$dump.nick$</a></b></div>
+ <a href="/u/$dump.nick$"><img height="50" width="50" src="$dump.avatar$" /></a>
<div class="content">$dump.content$</div>
<hr />
</div>
diff --git a/template/profile.st b/template/profile.st
index 04a28fb..b5d3f38 100755
--- a/template/profile.st
+++ b/template/profile.st
@@ -5,8 +5,8 @@
<link rel="stylesheet" type="text/css" media="screen"
href="/static/profile.css">
<script src="/static/jquery.editinplace.1.0.1.packed.js"
- type="text/javascript" >
- </script>
+ type="text/javascript"></script>
+ <script src="/static/js/ajaxupload.js"></script>
<script type="text/javascript" src="/static/jquery.editinplace.1.0.1.packed.js"></script>
<link type="text/css" href="http://jqueryui.com/latest/themes/base/ui.all.css" rel="stylesheet" />
@@ -15,7 +15,7 @@
<script>
jQuery(document).ready(function(){
initProfile();
- });
+ });
</script>
</head>
@@ -46,6 +46,8 @@
$if(is_home)$
<div id="avatar" class="editable">$avatar$</div>
+ <input id="upload" value="Upload" type="submit">
+ <img id="spinner" src="/static/spinner.gif" style="display: none" />
$endif$