diff options
| author | dumpfmprod <dumpfmprod@ubuntu.(none)> | 2010-02-24 08:11:03 -0500 |
|---|---|---|
| committer | dumpfmprod <dumpfmprod@ubuntu.(none)> | 2010-02-24 08:11:03 -0500 |
| commit | 7b9c7f610974c56f13e05387c1f5ff9d7ca16ad0 (patch) | |
| tree | 4086d35a5a4fa49808ec3a9f5fb8b0d9109ac93c | |
| parent | 0f8f1b234276514560e6df3bb6f6bac56fa9807d (diff) | |
| parent | 49ef1a688025d9183452fe5f1dccf7802d64dc62 (diff) | |
resolved merge
| -rwxr-xr-x | bin/repl.bat | 2 | ||||
| -rwxr-xr-x | src/cookie_login.clj | 5 | ||||
| -rw-r--r-- | src/image_utils.clj | 3 | ||||
| -rwxr-xr-x | src/site.clj | 209 | ||||
| -rwxr-xr-x | static/js/home.js | 8 | ||||
| -rwxr-xr-x | static/js/pichat.js | 51 | ||||
| -rwxr-xr-x | template/log.st | 7 | ||||
| -rwxr-xr-x | template/logged_dump.st | 1 | ||||
| -rwxr-xr-x | template/profile.st | 8 |
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 ☞</a></div> + <div id="pnavn"><a href="/$roomkey$/log/$next$">next ☞</a></div> $endif$ 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$ |
