On 3월27일, 오전5시07분, "Vyacheslav Akhmechet" <
coffee...@gmail.com> wrote:
> On 3/26/08, Leslie P. Polzer <
leslie.pol...@gmx.net> wrote:> Brian,
>
> > could you post the guts of your file upload stuff?
>
> If someone posts their code for uploads I'll be happy to clean it up
> and incorporate it into main repository.
Well, this is the basic code.
I'm not entirely sure that it works as I've give it here, since I'm in
the middle of finishing up drag and drop support for it with the
clipboard, and my current code depends on list presentations and views
-- I've cut all of that code out.
If it doesn't work, it should hopefully be fairly straight-forward to
fix -- I've put in the eval-when so that you can deal with it as a
single file, but that should probably be spliced out.
If not, then I'll set up a small project to test the cut-down version
in.
I notice that the repository version doesn't seem to support the
gridedit add view yet -- if that were added, then the filegrid widget
would be completely redundant -- getting rid of that would be nice.
I use the file object id for the filename, so you will have pub/files/
1, pub/files/2, etc.
This has the advantage of not letting bad people do terrible things
with file-names,
and the disadvantage that you need to look up the file object in order
to know what it is
and how to deal with it.
I've included a little code to handle requests like /pub/files?id=3
but I intend to replace that with a more generic
/pub/data/class/id/view form
e.g.,
/pub/data/file/3/icon would produce a view of the file object with id
3 as an icon.
Anyhow, it should hopefully be illustrative.
--
(eval-when (:compile-toplevel :load-toplevel)
(defclass file ()
((id
:reader file-id)
(name
:initform ""
:accessor file-name
:initarg file-name
:type string)
(type
:initform ""
:accessor file-type
:initarg file-type
:type string)
(meta-data
:initform nil
:accessor file-meta-data
:initarg file-meta-data
:type list)))
(defwidget filegrid (gridedit)
((item-add-view
:initform nil
:initarg :item-add-view
:reader filegrid-item-add-view)))
;;; render file-upload implementation
(defun render-file-upload (name value &key id (class "file-upload")
maxlength)
"Renders a file-upload field in a form."
(with-html
(:input :type :file :name (attributize-name name) :id id :value
value :class class)))
(defclass file-upload-presentation (text-presentation input-
presentation)
()
(:documentation "A presentation for file-uploads."))
(defmethod render-view-field-value (value (presentation file-upload-
presentation)
(field form-view-field) (view
form-view)
widget obj &rest args)
(declare (ignore args))
(render-file-upload (view-field-slot-name field) nil))
;;; File Upload
(defclass file-upload-parser (parser)
()
(:documentation "A parser designed to parse file upload values."))
(defmethod parse-view-field-value ((parser file-upload-parser) value
obj
(view form-view) (field form-view-
field) &rest args)
(declare (ignore args))
(when (listp value) (setf value (namestring (first value))))
(values t (text-input-present-p value) value)))
(defmethod gridedit-create-new-item-widget ((grid filegrid))
(let ((data (make-instance (datagrid-data-class grid))))
(make-instance 'dataform
:data data
:ui-state :form
:on-cancel (lambda (obj)
(declare (ignore obj))
(gridedit-reset-state grid)
(throw 'annihilate-dataform nil))
:on-success (lambda (obj)
(handle-file (request-parameter
"location") data)
(gridedit-add-item grid (dataform-
data obj))
(gridedit-reset-state grid)
(mark-dirty grid))
:on-close (lambda (obj)
(declare (ignore obj))
(gridedit-reset-state grid))
:data-view (gridedit-item-data-view grid)
:form-view (filegrid-item-add-view grid)
:form-title "Adding")))
(defmethod gridedit-add-item ((grid filegrid) item)
(when (gridedit-on-add-item grid)
(funcall (gridedit-on-add-item grid) grid item))
; dataform persists this item for us, no need to do it again
(flash-message (datagrid-flash grid) "Item added."))
;;; Add View
(defview file-add-view (:type form :inherit-from '(:scaffold
file) :default-method :post :enctype "multipart/form-data" :use-ajax-p
nil)
(id :hidep t)
(name :hidep t)
(type :hidep t)
(location :reader (lambda (obj) "") :writer (lambda (&rest args)
nil) :parse-as file-upload :present-as file-upload)
(meta-data :hidep t))
;;; Form View
(defview file-form-view (:type form :inherit-from '(:scaffold file))
(id :hidep t))
;;; Data View
(defview file-data-view (:type data :inherit-from '(:scaffold file))
(id :hidep t))
(defmethod weblocks-memory:strictly-less-p ((a file) (b file))
(< (file-id a) (file-id b)))
(defun handle-file (post-parameter file)
(when (and post-parameter (listp post-parameter))
(destructuring-bind (path name ct) post-parameter
;; strip directory info sent by Windows browsers
(when (search "Windows" (hunchentoot:user-agent) :test #'char-
equal)
(setq name (cl-ppcre:regex-replace ".*\\\\" name "")))
(let ((new-path (merge-pathnames
(merge-pathnames
(make-pathname :directory `(:relative "pub"
"files"))
; (parse-namestring name)
; make sure they're unique
(make-pathname :name (format nil "~D" (file-
id file))))
(asdf-system-directory :x))))
(rename-file path (ensure-directories-exist new-path))
(setf (file-name file) (pathname-name (parse-namestring name))
(file-type file) ct)
(persist-object (object-store file) file)))))
; --- a little extra to retrieve files
(defun handle-file-request ()
(let* ((id (request-parameter "id"))
(file (find-persistent-object-by-id *prevalence-store* 'file
(parse-integer id :radix 10)))
(path (merge-pathnames
(make-pathname :directory `(:relative "pub"
"files") :name id)
(asdf-system-directory :x))))
(hunchentoot:handle-static-file path (file-type file))))
(push (hunchentoot:create-prefix-dispatcher "/pub/files" 'handle-
file-request) hunchentoot:*dispatch-table*)