Skip to content

Instantly share code, notes, and snippets.

@ostronom
Last active August 29, 2015 14:23
Show Gist options
  • Save ostronom/f4bece969e0fb8956ec8 to your computer and use it in GitHub Desktop.
Save ostronom/f4bece969e0fb8956ec8 to your computer and use it in GitHub Desktop.
(ns myns
(:require-macros [cljs.core.async.macros :refer [go-loop]])
(:require [ajax.core :refer [ajax-request raw-response-format]]))
(def html5supported?
(not (or (undefined? js/File)
(undefined? js/Blob)
(undefined? js/FileList)
(not (or js/Blob.prototype.webkitSlice js/Blob.prototype.mozSlice js/Blob.prototype.slice false)))))
(def slicer
(if html5supported?
(cond
js/Blob.prototype.slice #(.slice %1 %2 %3)
js/Blob.prototype.mozSlice #(.mozSlice %1 %2 %3)
js/Blob.prototype.webkitSlice #(.webkitSlice %1 %2 %3))))
(defn inject-data [form data]
(doseq [[k v] data] (.append form k v)))
(defprotocol IUploader
" Maybe, target must be target-fn to distinguish between classic/chunked endpoint. "
(upload [this target file form-data]))
(defrecord ClassicUploader []
IUploader
(upload [_ target file form-data]
(let [res (chan)
params (doto (js/FormData.) (.append "file" file) (inject-data form-data))
xhr (ajax-request {:method :post :params params :uri target :handler #(do (put! res [:complete]) (close! res))})]
(aset (.-upload xhr) "onprogress" #(put! res [:progress {:total (.-total %) :loaded (.-loaded %)}]))
res)))
(defn upload-chunk [target file form-data upload-id start-offset end-offset]
(let [res (chan)
size (.-size file)
params (doto (js/FormData.)
(inject-data form-data)
(.append "id" upload-id)
(.append "offset" start-offset)
(.append "total" size)
(.append "file" (slicer file start-offset end-offset (.-type file)) (.-name file)))]
(if (>= start-offset size)
(put! res :done)
(ajax-request {:uri target
:params params
:response-format (raw-response-format)
:method :post
:handler (fn [[ok result]] (put! res (if ok :done :error)))}))
res))
(defrecord ChunkedUploader [chunk-size]
IUploader
(upload [_ target file form-data]
(let [upload-id (-> (js/Date.) .getTime str)
res (chan)
upload #(let [start (* chunk-size %) end (+ start chunk-size)]
(upload-chunk target file form-data upload-id start end))
max-chunks (max 1 (js/Math.ceil (/ (.-size file) chunk-size)))]
(go-loop [chunk 0]
(when-let [e (<! (upload chunk))]
(when (and (= e :done) (= chunk max-chunks))
(put! res [:complete])
(close! res))
(when (and (= e :done) (not= chunk max-chunks))
(put! res [:progress {:loaded chunk :total max-chunks}])
(recur (inc chunk)))
(when (= e :error)
(put! res [:error])
(recur chunk))))
res)))
(defn get-uploader []
(if html5supported? (ChunkedUploader. (* 512 1024)) (ClassicUploader.)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment