Permalink
Browse files

more example app work and building out supporting libraries

  • Loading branch information...
1 parent 1afcb9d commit 12c242124735a69a264a4d40b16622709826c7a0 @mmcgrana committed Jan 10, 2009
View
@@ -0,0 +1 @@
+.DS_Store
@@ -1 +1,18 @@
-Filesystem manipulation utilities for Clojure, wrapping Apache Commons IO.
+h1. clj-file-utils
+
+Unix-like filesystem manipulation utilities for Clojure, wrapping Apache Commons IO.
+
+h2. Implemented Functions
+
+ file
+ size
+ mv
+ cp
+ cp-r
+ rm
+ rm-f
+ rm-r
+ rm-rf
+ touch
+ mkdirp
+ chmod
View
@@ -1,12 +1,2 @@
-touch
-mkdir
-mkdir-p
-rm
-rm-r
-cp-r
-mv
-size
-chmod
-
tests
documentation
@@ -1,6 +1,7 @@
(ns clj-file-utils.core
- (:import java.io.File
- org.apache.commons.io.FileUtils))
+ (:import (java.io File IOException)
+ org.apache.commons.io.FileUtils)
+ (:use clojure.contrib.shell-out))
(defn file
"Returns an instance of java.io.File based on the given file name (or
@@ -11,11 +12,62 @@
([p q & names] (reduce file (file p q) names)))
(defn size
- "Returns the size in bytes of the file."
+ "Returns the size in bytes of a file."
[#^File file]
(.length file))
+(defn mv
+ "Move a file from one location to another, preserving the file data."
+ [#^File from-file #^File to-file]
+ (FileUtils/moveFile from-file to-file))
+
(defn cp
"Copy a file from one location to another, preserving the file date."
[#^File from-file #^File to-file]
(FileUtils/copyFile from-file to-file))
+
+(defn cp-r
+ "Copy a directory from one location to another, preseing the file data."
+ [#^File from-dir #^File to-dir]
+ (FileUtils/copyDirectory from-dir to-dir))
+
+(defn rm
+ "Remove a file. Will throw an exception if the file cannot be deleted."
+ [#^File file]
+ (if-not (.delete file)
+ (throw (IOException.))))
+
+(defn rm-f
+ "Remove a file, ignoring any errors."
+ [#^File file]
+ (FileUtils/forceDelete file))
+
+(defn rm-r
+ "REmove a directory. The directory must be empty; will throw an exception
+ if it is not or if the file cannot be deleted."
+ [#^File dir]
+ (if-not (.delete dir)
+ (throw (IOException.))))
+
+(defn rm-rf
+ "Remove a directory, ignoring any errors."
+ [#^File dir]
+ (FileUtils/forceDelete dir))
+
+(defn touch
+ "'touch' as file, as with the Unix command."
+ [#^File file]
+ (FileUtils/touch file))
+
+(defn mkdir-p
+ "Create the directory, including any required but nonexist parents.
+ The method does not throw an exception if the complete directory tree
+ already exists."
+ [#^File file]
+ (when-not (.exists file)
+ (FileUtils/forceMkdir file)))
+
+(defn chmod
+ "'chmod' a file to a mode given as a 4-character string."
+ [#^File file #^String mode]
+ (sh "chmod" mode (.getAbsolutePath file)))
@@ -45,9 +45,9 @@
(html [:input {:type "file" :name name}]))
(defn submit-tag
- "Return html for a submit button with value as the text."
- [value]
- (html [:input {:type "submit" :name "commit" :value value}]))
+ "Return html for a submit button with the given text."
+ [text]
+ (html [:input {:type "submit" :name "commit" :value text}]))
(defn form-to
"Returns html for a form."
@@ -65,9 +65,16 @@
body]))))
(defn link-to
- "Returns html for a link with anchor text to the path."
- [anchor path]
- (html [:a {:href path} anchor]))
+ "Returns html for a link with anchor text to the url."
+ [text url]
+ (html [:a {:href url} text]))
+
+(defn delete-button
+ "Returns html for a form consisting only of a button that, when clicked,
+ will send a delete request to the given path."
+ [text url]
+ (form-to [:delete url]
+ (submit-tag text)))
(def #^{:private true} mime-type-strs
{:rss "application/rss+xml"
@@ -37,4 +37,8 @@
(deftest "link-to"
(assert= "<a href=\"http://google.com\">foo</a>"
- (link-to "foo" "http://google.com")))
+ (link-to "foo" "http://google.com")))
+
+(deftest "delete-button"
+ (assert= "<form method=\"post\" action=\"/foo\"><input type=\"hidden\" name=\"_method\" value=\"delete\" /><input name=\"commit\" type=\"submit\" value=\"Delete\" /></form>"
+ (delete-button "Delete" "/foo")))
View
@@ -12,4 +12,5 @@ wrap-if in ring.builder, other builder tools
basic_stack.clj
account for all dependencies
server boot fits in a tweet
-tests
+tests
+colorize show exception traces
@@ -21,19 +21,20 @@
(def app-host "http://localhost:8080")
(def public-dir (file-utils/file "public"))
(def uploads-dir (file-utils/file "public/uploads"))
-(def reloadable-namespace-syms '(ringup.app))
+(def reloadable-namespace-syms '(weldup.app))
(def data-source
(pg-data-source {:database "weldup_dev" :user "mmcgrana" :password ""}))
(def logger (logger4j :err :info))
;; Routing
(routing/defrouting
app-host
- [['ringup.app 'index :index :get "/"]
- ['ringup.app 'new :new :get "/new"]
- ['ringup.app 'create :create :put "/"]
- ['ringup.app 'show :show :get "/:id"]
- ['ringup.app 'not-found :not-found :any "/:path" {:path ".*"}]])
+ [['weldup.app 'index :index :get "/"]
+ ['weldup.app 'new :new :get "/new"]
+ ['weldup.app 'create :create :put "/"]
+ ['weldup.app 'show :show :get "/:id"]
+ ['weldup.app 'destroy :destroy :delete "/:id"]
+ ['weldup.app 'not-found :not-found :any "/:path" {:path ".*"}]])
;; Models
(stash/defmodel +upload+
@@ -55,11 +56,16 @@
(re-gsub #"(?i)[^a-z0-9_.]" "_" filename))
(defn create-upload [upload-map]
- (let [upload (stash/create +upload+
- {:filename (normalize-filename (:filename upload-map))
- :content_type (:content-type upload-map)
- :size (:size upload-map)})]
- (file-utils/cp (:tempfile upload-map) (upload-file upload))))
+ (stash/transaction +upload+
+ (let [upload (stash/create +upload+
+ {:filename (normalize-filename (:filename upload-map))
+ :content_type (:content-type upload-map)
+ :size (:size upload-map)})]
+ (file-utils/cp (:tempfile upload-map) (upload-file upload)))))
+
+(defn destroy-upload [upload]
+ (stash/destroy upload)
+ (file-utils/rm-f (upload-file upload)))
;; Views
(defmacro with-layout
@@ -74,9 +80,11 @@
(defn index-view [uploads]
(with-layout
[:p [:a {:href (path :new)} "new upload"]]
- [:h3 "Uploaded"]
+ [:h3 (if (> (count uploads) 0) "Uploaded" "None Uploaded Yet")]
(domap-str [upload uploads]
- (html [:p [:a {:href (path :show upload)} (h (:filename upload))]]))))
+ (html
+ [:p [:a {:href (path :show upload)} (h (:filename upload))]]
+ (delete-button "Delete" (path :destroy upload))))))
(defn new-view []
(with-layout
@@ -87,7 +95,7 @@
(submit-tag "Upload")))))
;; Controllers
-(defn not-found [req]
+(defn not-found [& [req]]
(redirect (path :index)))
(defn index [req]
@@ -100,10 +108,20 @@
(create-upload (params req :upload))
(redirect (path :index)))
+(defmacro with-upload
+ [[binding-sym id-form] & body]
+ `(if-let [~binding-sym (stash/find-one +upload+ {:where [:id := ~id-form]})]
+ (do ~@body)
+ (not-found)))
+
(defn show [req]
- (if-let [upload (stash/find-one +upload+ {:where [:id := (params req :id)]})]
- (send-file (upload-file upload) {:filename (:filename upload)})
- (not-found [req])))
+ (with-upload [upload (params req :id)]
+ (send-file (upload-file upload) {:filename (:filename upload)})))
+
+(defn destroy [req]
+ (with-upload [upload (params req :id)]
+ (destroy-upload upload)
+ (redirect (path :index))))
;; Ring app
(def app
@@ -4,7 +4,7 @@
(def mock-upload
(upload
- (file-utils/file "test" "ringup" "assets" "test.png")
+ (file-utils/file "test" "weldup" "assets" "test.png")
"image/png"
"test_filename.png"))
View
@@ -1 +1,2 @@
More thorough testing
+Set up upload test helper so that it emulates the GC situation
View
@@ -194,6 +194,7 @@
disk-file-item-factory
(doto (DiskFileItemFactory.)
(.setSizeThreshold -1)
+ (.setRepository (java.io.File. "/Users/mmcgrana/Desktop/git/clj-garden/weld-upload-example/public/uploads"))
(.setFileCleaningTracker nil)))
(defn- parse-multipart-params
@@ -207,17 +208,19 @@
(getContentType [] (content-type request))
(getContentLength [] (content-length request))
(getCharacterEncoding [] (character-encoding request))
- (getInputStream [] ((request :env) :stream)))
+ (getInputStream [] ((request :env) :body)))
items (.parseRequest upload context)
pairs (map
(fn [#^DiskFileItem item]
[(.getFieldName item)
(if (.isFormField item)
(.getString item)
- {:filename (.getName item)
- :size (.getSize item)
- :content-type (.getContentType item)
- :tempfile (.getStoreLocation item)})])
+ ; need first pair to prevent premature tempfile GC
+ {:disk-file-item item
+ :filename (.getName item)
+ :size (.getSize item)
+ :content-type (.getContentType item)
+ :tempfile (.getStoreLocation item)})])
items)]
(pairs-parse pairs)))))
@@ -16,7 +16,7 @@
(defn upload
"Returns an upload hash that can be used as a value in the :params map for
the mock request helper."
- [[file content-type filename]]
+ [file content-type filename]
{:tempfile file :size (file-utils/size file)
:filename filename :content-type content-type})

0 comments on commit 12c2421

Please sign in to comment.