diff options
author | Sem <sembrestels@riseup.net> | 2012-07-29 10:15:17 +0200 |
---|---|---|
committer | Sem <sembrestels@riseup.net> | 2012-07-29 10:15:17 +0200 |
commit | d00e34131d6177f6d22eb3cf32c50216820aafba (patch) | |
tree | 8adfab83855f19bc1de2273216707da99bec9c1d /vendors/jquery-file-upload/server/gae-go | |
parent | 24ff6662195222479b4d83d41fa89edc8a3c05d1 (diff) | |
download | elgg-d00e34131d6177f6d22eb3cf32c50216820aafba.tar.gz elgg-d00e34131d6177f6d22eb3cf32c50216820aafba.tar.bz2 |
Added jquery file upload plugin: https://github.com/blueimp/jQuery-File-Upload
Diffstat (limited to 'vendors/jquery-file-upload/server/gae-go')
-rw-r--r-- | vendors/jquery-file-upload/server/gae-go/app.yaml | 12 | ||||
-rw-r--r-- | vendors/jquery-file-upload/server/gae-go/app/main.go | 361 | ||||
-rw-r--r-- | vendors/jquery-file-upload/server/gae-go/resize/resize.go | 247 | ||||
-rw-r--r-- | vendors/jquery-file-upload/server/gae-go/static/favicon.ico | bin | 0 -> 1150 bytes | |||
-rw-r--r-- | vendors/jquery-file-upload/server/gae-go/static/robots.txt | 2 |
5 files changed, 622 insertions, 0 deletions
diff --git a/vendors/jquery-file-upload/server/gae-go/app.yaml b/vendors/jquery-file-upload/server/gae-go/app.yaml new file mode 100644 index 000000000..2d09daa56 --- /dev/null +++ b/vendors/jquery-file-upload/server/gae-go/app.yaml @@ -0,0 +1,12 @@ +application: jquery-file-upload +version: 2 +runtime: go +api_version: go1 + +handlers: +- url: /(favicon\.ico|robots\.txt) + static_files: static/\1 + upload: static/(.*) + expiration: '1d' +- url: /.* + script: _go_app diff --git a/vendors/jquery-file-upload/server/gae-go/app/main.go b/vendors/jquery-file-upload/server/gae-go/app/main.go new file mode 100644 index 000000000..01dc2f204 --- /dev/null +++ b/vendors/jquery-file-upload/server/gae-go/app/main.go @@ -0,0 +1,361 @@ +/* + * jQuery File Upload Plugin GAE Go Example 2.0 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2011, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +package app + +import ( + "appengine" + "appengine/blobstore" + "appengine/memcache" + "appengine/taskqueue" + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "image" + "image/png" + "io" + "log" + "mime/multipart" + "net/http" + "net/url" + "regexp" + "resize" + "strings" + "time" +) + +import _ "image/gif" +import _ "image/jpeg" + +const ( + WEBSITE = "http://blueimp.github.com/jQuery-File-Upload/" + MIN_FILE_SIZE = 1 // bytes + MAX_FILE_SIZE = 5000000 // bytes + IMAGE_TYPES = "image/(gif|p?jpeg|(x-)?png)" + ACCEPT_FILE_TYPES = IMAGE_TYPES + EXPIRATION_TIME = 300 // seconds + THUMBNAIL_MAX_WIDTH = 80 + THUMBNAIL_MAX_HEIGHT = THUMBNAIL_MAX_WIDTH +) + +var ( + imageTypes = regexp.MustCompile(IMAGE_TYPES) + acceptFileTypes = regexp.MustCompile(ACCEPT_FILE_TYPES) +) + +type FileInfo struct { + Key appengine.BlobKey `json:"-"` + Url string `json:"url,omitempty"` + ThumbnailUrl string `json:"thumbnail_url,omitempty"` + Name string `json:"name"` + Type string `json:"type"` + Size int64 `json:"size"` + Error string `json:"error,omitempty"` + DeleteUrl string `json:"delete_url,omitempty"` + DeleteType string `json:"delete_type,omitempty"` +} + +func (fi *FileInfo) ValidateType() (valid bool) { + if acceptFileTypes.MatchString(fi.Type) { + return true + } + fi.Error = "acceptFileTypes" + return false +} + +func (fi *FileInfo) ValidateSize() (valid bool) { + if fi.Size < MIN_FILE_SIZE { + fi.Error = "minFileSize" + } else if fi.Size > MAX_FILE_SIZE { + fi.Error = "maxFileSize" + } else { + return true + } + return false +} + +func (fi *FileInfo) CreateUrls(r *http.Request, c appengine.Context) { + u := &url.URL{ + Scheme: r.URL.Scheme, + Host: appengine.DefaultVersionHostname(c), + Path: "/", + } + uString := u.String() + fi.Url = uString + escape(string(fi.Key)) + "/" + + escape(string(fi.Name)) + fi.DeleteUrl = fi.Url + fi.DeleteType = "DELETE" + if fi.ThumbnailUrl != "" && -1 == strings.Index( + r.Header.Get("Accept"), + "application/json", + ) { + fi.ThumbnailUrl = uString + "thumbnails/" + + escape(string(fi.Key)) + } +} + +func (fi *FileInfo) CreateThumbnail(r io.Reader, c appengine.Context) (data []byte, err error) { + defer func() { + if rec := recover(); rec != nil { + log.Println(rec) + // 1x1 pixel transparent GIf, bas64 encoded: + s := "R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" + data, _ = base64.StdEncoding.DecodeString(s) + fi.ThumbnailUrl = "data:image/gif;base64," + s + } + memcache.Add(c, &memcache.Item{ + Key: string(fi.Key), + Value: data, + Expiration: EXPIRATION_TIME, + }) + }() + img, _, err := image.Decode(r) + check(err) + if bounds := img.Bounds(); bounds.Dx() > THUMBNAIL_MAX_WIDTH || + bounds.Dy() > THUMBNAIL_MAX_HEIGHT { + w, h := THUMBNAIL_MAX_WIDTH, THUMBNAIL_MAX_HEIGHT + if bounds.Dx() > bounds.Dy() { + h = bounds.Dy() * h / bounds.Dx() + } else { + w = bounds.Dx() * w / bounds.Dy() + } + img = resize.Resize(img, img.Bounds(), w, h) + } + var b bytes.Buffer + err = png.Encode(&b, img) + check(err) + data = b.Bytes() + fi.ThumbnailUrl = "data:image/png;base64," + + base64.StdEncoding.EncodeToString(data) + return +} + +func check(err error) { + if err != nil { + panic(err) + } +} + +func escape(s string) string { + return strings.Replace(url.QueryEscape(s), "+", "%20", -1) +} + +func delayedDelete(c appengine.Context, fi *FileInfo) { + if key := string(fi.Key); key != "" { + task := &taskqueue.Task{ + Path: "/" + escape(key) + "/-", + Method: "DELETE", + Delay: time.Duration(EXPIRATION_TIME) * time.Second, + } + taskqueue.Add(c, task, "") + } +} + +func handleUpload(r *http.Request, p *multipart.Part) (fi *FileInfo) { + fi = &FileInfo{ + Name: p.FileName(), + Type: p.Header.Get("Content-Type"), + } + if !fi.ValidateType() { + return + } + defer func() { + if rec := recover(); rec != nil { + log.Println(rec) + fi.Error = rec.(error).Error() + } + }() + var b bytes.Buffer + lr := &io.LimitedReader{R: p, N: MAX_FILE_SIZE + 1} + context := appengine.NewContext(r) + w, err := blobstore.Create(context, fi.Type) + defer func() { + w.Close() + fi.Size = MAX_FILE_SIZE + 1 - lr.N + fi.Key, err = w.Key() + check(err) + if !fi.ValidateSize() { + err := blobstore.Delete(context, fi.Key) + check(err) + return + } + delayedDelete(context, fi) + if b.Len() > 0 { + fi.CreateThumbnail(&b, context) + } + fi.CreateUrls(r, context) + }() + check(err) + var wr io.Writer = w + if imageTypes.MatchString(fi.Type) { + wr = io.MultiWriter(&b, w) + } + _, err = io.Copy(wr, lr) + return +} + +func getFormValue(p *multipart.Part) string { + var b bytes.Buffer + io.CopyN(&b, p, int64(1<<20)) // Copy max: 1 MiB + return b.String() +} + +func handleUploads(r *http.Request) (fileInfos []*FileInfo) { + fileInfos = make([]*FileInfo, 0) + mr, err := r.MultipartReader() + check(err) + r.Form, err = url.ParseQuery(r.URL.RawQuery) + check(err) + part, err := mr.NextPart() + for err == nil { + if name := part.FormName(); name != "" { + if part.FileName() != "" { + fileInfos = append(fileInfos, handleUpload(r, part)) + } else { + r.Form[name] = append(r.Form[name], getFormValue(part)) + } + } + part, err = mr.NextPart() + } + return +} + +func get(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/" { + http.Redirect(w, r, WEBSITE, http.StatusFound) + return + } + parts := strings.Split(r.URL.Path, "/") + if len(parts) == 3 { + if key := parts[1]; key != "" { + blobKey := appengine.BlobKey(key) + bi, err := blobstore.Stat(appengine.NewContext(r), blobKey) + if err == nil { + w.Header().Add( + "Cache-Control", + fmt.Sprintf("public,max-age=%d", EXPIRATION_TIME), + ) + if imageTypes.MatchString(bi.ContentType) { + w.Header().Add("X-Content-Type-Options", "nosniff") + } else { + w.Header().Add("Content-Type", "application/octet-stream") + w.Header().Add( + "Content-Disposition:", + fmt.Sprintf("attachment; filename=%s;", parts[2]), + ) + } + blobstore.Send(w, appengine.BlobKey(key)) + return + } + } + } + http.Error(w, "404 Not Found", http.StatusNotFound) +} + +func post(w http.ResponseWriter, r *http.Request) { + b, err := json.Marshal(handleUploads(r)) + check(err) + if redirect := r.FormValue("redirect"); redirect != "" { + http.Redirect(w, r, fmt.Sprintf( + redirect, + escape(string(b)), + ), http.StatusFound) + return + } + jsonType := "application/json" + if strings.Index(r.Header.Get("Accept"), jsonType) != -1 { + w.Header().Set("Content-Type", jsonType) + } + fmt.Fprintln(w, string(b)) +} + +func delete(w http.ResponseWriter, r *http.Request) { + parts := strings.Split(r.URL.Path, "/") + if len(parts) != 3 { + return + } + if key := parts[1]; key != "" { + c := appengine.NewContext(r) + blobstore.Delete(c, appengine.BlobKey(key)) + memcache.Delete(c, key) + } +} + +func serveThumbnail(w http.ResponseWriter, r *http.Request) { + parts := strings.Split(r.URL.Path, "/") + if len(parts) == 3 { + if key := parts[2]; key != "" { + var data []byte + c := appengine.NewContext(r) + item, err := memcache.Get(c, key) + if err == nil { + data = item.Value + } else { + blobKey := appengine.BlobKey(key) + if _, err = blobstore.Stat(c, blobKey); err == nil { + fi := FileInfo{Key: blobKey} + data, _ = fi.CreateThumbnail( + blobstore.NewReader(c, blobKey), + c, + ) + } + } + if err == nil && len(data) > 3 { + w.Header().Add( + "Cache-Control", + fmt.Sprintf("public,max-age=%d", EXPIRATION_TIME), + ) + contentType := "image/png" + if string(data[:3]) == "GIF" { + contentType = "image/gif" + } else if string(data[1:4]) != "PNG" { + contentType = "image/jpeg" + } + w.Header().Set("Content-Type", contentType) + fmt.Fprintln(w, string(data)) + return + } + } + } + http.Error(w, "404 Not Found", http.StatusNotFound) +} + +func handle(w http.ResponseWriter, r *http.Request) { + params, err := url.ParseQuery(r.URL.RawQuery) + check(err) + w.Header().Add("Access-Control-Allow-Origin", "*") + w.Header().Add( + "Access-Control-Allow-Methods", + "OPTIONS, HEAD, GET, POST, PUT, DELETE", + ) + switch r.Method { + case "OPTIONS": + case "HEAD": + case "GET": + get(w, r) + case "POST": + if len(params["_method"]) > 0 && params["_method"][0] == "DELETE" { + delete(w, r) + } else { + post(w, r) + } + case "DELETE": + delete(w, r) + default: + http.Error(w, "501 Not Implemented", http.StatusNotImplemented) + } +} + +func init() { + http.HandleFunc("/", handle) + http.HandleFunc("/thumbnails/", serveThumbnail) +} diff --git a/vendors/jquery-file-upload/server/gae-go/resize/resize.go b/vendors/jquery-file-upload/server/gae-go/resize/resize.go new file mode 100644 index 000000000..dcb627870 --- /dev/null +++ b/vendors/jquery-file-upload/server/gae-go/resize/resize.go @@ -0,0 +1,247 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package resize + +import ( + "image" + "image/color" +) + +// Resize returns a scaled copy of the image slice r of m. +// The returned image has width w and height h. +func Resize(m image.Image, r image.Rectangle, w, h int) image.Image { + if w < 0 || h < 0 { + return nil + } + if w == 0 || h == 0 || r.Dx() <= 0 || r.Dy() <= 0 { + return image.NewRGBA64(image.Rect(0, 0, w, h)) + } + switch m := m.(type) { + case *image.RGBA: + return resizeRGBA(m, r, w, h) + case *image.YCbCr: + if m, ok := resizeYCbCr(m, r, w, h); ok { + return m + } + } + ww, hh := uint64(w), uint64(h) + dx, dy := uint64(r.Dx()), uint64(r.Dy()) + // The scaling algorithm is to nearest-neighbor magnify the dx * dy source + // to a (ww*dx) * (hh*dy) intermediate image and then minify the intermediate + // image back down to a ww * hh destination with a simple box filter. + // The intermediate image is implied, we do not physically allocate a slice + // of length ww*dx*hh*dy. + // For example, consider a 4*3 source image. Label its pixels from a-l: + // abcd + // efgh + // ijkl + // To resize this to a 3*2 destination image, the intermediate is 12*6. + // Whitespace has been added to delineate the destination pixels: + // aaab bbcc cddd + // aaab bbcc cddd + // eeef ffgg ghhh + // + // eeef ffgg ghhh + // iiij jjkk klll + // iiij jjkk klll + // Thus, the 'b' source pixel contributes one third of its value to the + // (0, 0) destination pixel and two thirds to (1, 0). + // The implementation is a two-step process. First, the source pixels are + // iterated over and each source pixel's contribution to 1 or more + // destination pixels are summed. Second, the sums are divided by a scaling + // factor to yield the destination pixels. + // TODO: By interleaving the two steps, instead of doing all of + // step 1 first and all of step 2 second, we could allocate a smaller sum + // slice of length 4*w*2 instead of 4*w*h, although the resultant code + // would become more complicated. + n, sum := dx*dy, make([]uint64, 4*w*h) + for y := r.Min.Y; y < r.Max.Y; y++ { + for x := r.Min.X; x < r.Max.X; x++ { + // Get the source pixel. + r32, g32, b32, a32 := m.At(x, y).RGBA() + r64 := uint64(r32) + g64 := uint64(g32) + b64 := uint64(b32) + a64 := uint64(a32) + // Spread the source pixel over 1 or more destination rows. + py := uint64(y) * hh + for remy := hh; remy > 0; { + qy := dy - (py % dy) + if qy > remy { + qy = remy + } + // Spread the source pixel over 1 or more destination columns. + px := uint64(x) * ww + index := 4 * ((py/dy)*ww + (px / dx)) + for remx := ww; remx > 0; { + qx := dx - (px % dx) + if qx > remx { + qx = remx + } + sum[index+0] += r64 * qx * qy + sum[index+1] += g64 * qx * qy + sum[index+2] += b64 * qx * qy + sum[index+3] += a64 * qx * qy + index += 4 + px += qx + remx -= qx + } + py += qy + remy -= qy + } + } + } + return average(sum, w, h, n*0x0101) +} + +// average convert the sums to averages and returns the result. +func average(sum []uint64, w, h int, n uint64) image.Image { + ret := image.NewRGBA(image.Rect(0, 0, w, h)) + for y := 0; y < h; y++ { + for x := 0; x < w; x++ { + index := 4 * (y*w + x) + ret.SetRGBA(x, y, color.RGBA{ + uint8(sum[index+0] / n), + uint8(sum[index+1] / n), + uint8(sum[index+2] / n), + uint8(sum[index+3] / n), + }) + } + } + return ret +} + +// resizeYCbCr returns a scaled copy of the YCbCr image slice r of m. +// The returned image has width w and height h. +func resizeYCbCr(m *image.YCbCr, r image.Rectangle, w, h int) (image.Image, bool) { + var verticalRes int + switch m.SubsampleRatio { + case image.YCbCrSubsampleRatio420: + verticalRes = 2 + case image.YCbCrSubsampleRatio422: + verticalRes = 1 + default: + return nil, false + } + ww, hh := uint64(w), uint64(h) + dx, dy := uint64(r.Dx()), uint64(r.Dy()) + // See comment in Resize. + n, sum := dx*dy, make([]uint64, 4*w*h) + for y := r.Min.Y; y < r.Max.Y; y++ { + Y := m.Y[y*m.YStride:] + Cb := m.Cb[y/verticalRes*m.CStride:] + Cr := m.Cr[y/verticalRes*m.CStride:] + for x := r.Min.X; x < r.Max.X; x++ { + // Get the source pixel. + r8, g8, b8 := color.YCbCrToRGB(Y[x], Cb[x/2], Cr[x/2]) + r64 := uint64(r8) + g64 := uint64(g8) + b64 := uint64(b8) + // Spread the source pixel over 1 or more destination rows. + py := uint64(y) * hh + for remy := hh; remy > 0; { + qy := dy - (py % dy) + if qy > remy { + qy = remy + } + // Spread the source pixel over 1 or more destination columns. + px := uint64(x) * ww + index := 4 * ((py/dy)*ww + (px / dx)) + for remx := ww; remx > 0; { + qx := dx - (px % dx) + if qx > remx { + qx = remx + } + qxy := qx * qy + sum[index+0] += r64 * qxy + sum[index+1] += g64 * qxy + sum[index+2] += b64 * qxy + sum[index+3] += 0xFFFF * qxy + index += 4 + px += qx + remx -= qx + } + py += qy + remy -= qy + } + } + } + return average(sum, w, h, n), true +} + +// resizeRGBA returns a scaled copy of the RGBA image slice r of m. +// The returned image has width w and height h. +func resizeRGBA(m *image.RGBA, r image.Rectangle, w, h int) image.Image { + ww, hh := uint64(w), uint64(h) + dx, dy := uint64(r.Dx()), uint64(r.Dy()) + // See comment in Resize. + n, sum := dx*dy, make([]uint64, 4*w*h) + for y := r.Min.Y; y < r.Max.Y; y++ { + pixOffset := m.PixOffset(r.Min.X, y) + for x := r.Min.X; x < r.Max.X; x++ { + // Get the source pixel. + r64 := uint64(m.Pix[pixOffset+0]) + g64 := uint64(m.Pix[pixOffset+1]) + b64 := uint64(m.Pix[pixOffset+2]) + a64 := uint64(m.Pix[pixOffset+3]) + pixOffset += 4 + // Spread the source pixel over 1 or more destination rows. + py := uint64(y) * hh + for remy := hh; remy > 0; { + qy := dy - (py % dy) + if qy > remy { + qy = remy + } + // Spread the source pixel over 1 or more destination columns. + px := uint64(x) * ww + index := 4 * ((py/dy)*ww + (px / dx)) + for remx := ww; remx > 0; { + qx := dx - (px % dx) + if qx > remx { + qx = remx + } + qxy := qx * qy + sum[index+0] += r64 * qxy + sum[index+1] += g64 * qxy + sum[index+2] += b64 * qxy + sum[index+3] += a64 * qxy + index += 4 + px += qx + remx -= qx + } + py += qy + remy -= qy + } + } + } + return average(sum, w, h, n) +} + +// Resample returns a resampled copy of the image slice r of m. +// The returned image has width w and height h. +func Resample(m image.Image, r image.Rectangle, w, h int) image.Image { + if w < 0 || h < 0 { + return nil + } + if w == 0 || h == 0 || r.Dx() <= 0 || r.Dy() <= 0 { + return image.NewRGBA64(image.Rect(0, 0, w, h)) + } + curw, curh := r.Dx(), r.Dy() + img := image.NewRGBA(image.Rect(0, 0, w, h)) + for y := 0; y < h; y++ { + for x := 0; x < w; x++ { + // Get a source pixel. + subx := x * curw / w + suby := y * curh / h + r32, g32, b32, a32 := m.At(subx, suby).RGBA() + r := uint8(r32 >> 8) + g := uint8(g32 >> 8) + b := uint8(b32 >> 8) + a := uint8(a32 >> 8) + img.SetRGBA(x, y, color.RGBA{r, g, b, a}) + } + } + return img +} diff --git a/vendors/jquery-file-upload/server/gae-go/static/favicon.ico b/vendors/jquery-file-upload/server/gae-go/static/favicon.ico Binary files differnew file mode 100644 index 000000000..1a71ea772 --- /dev/null +++ b/vendors/jquery-file-upload/server/gae-go/static/favicon.ico diff --git a/vendors/jquery-file-upload/server/gae-go/static/robots.txt b/vendors/jquery-file-upload/server/gae-go/static/robots.txt new file mode 100644 index 000000000..eb0536286 --- /dev/null +++ b/vendors/jquery-file-upload/server/gae-go/static/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: |