diff --git a/api/controller/app.go b/api/controller/app.go index c5fde52b2..dcd142efa 100644 --- a/api/controller/app.go +++ b/api/controller/app.go @@ -17,8 +17,8 @@ import ( "github.com/goodrain/rainbond/api/model" "github.com/goodrain/rainbond/db" httputil "github.com/goodrain/rainbond/util/http" - "github.com/goodrain/rainbond/api/controller/coquelicot.v1" "github.com/jinzhu/gorm" + "github.com/goodrain/rainbond/api/controller/upload" ) type AppStruct struct{} @@ -168,17 +168,8 @@ func (a *AppStruct) NewUpload(w http.ResponseWriter, r *http.Request) { } dirName := fmt.Sprintf("%s/import/%s", handler.GetAppHandler().GetStaticDir(), eventId) - he := coquelicot.NewStorage(dirName) - he.UploadHandler(w, r) - case "GET": - if eventId == "" { - httputil.ReturnError(r, w, 500, "Failed to parse eventId.") - return - } - dirName := fmt.Sprintf("%s/import/%s", handler.GetAppHandler().GetStaticDir(), eventId) - - he := coquelicot.NewStorage(dirName) - he.ResumeHandler(w, r) + st := upload.NewStorage(dirName) + st.UploadHandler(w, r) } } @@ -276,7 +267,7 @@ func (a *AppStruct) ImportApp(w http.ResponseWriter, r *http.Request) { res, err := db.GetManager().AppDao().GetByEventId(eventId) if err != nil { - if err == gorm.ErrRecordNotFound{ + if err == gorm.ErrRecordNotFound { res.Status = "uploading" httputil.ReturnSuccess(r, w, res) return diff --git a/api/controller/coquelicot.v1/.gitignore b/api/controller/coquelicot.v1/.gitignore deleted file mode 100644 index 065c2e0a8..000000000 --- a/api/controller/coquelicot.v1/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -*.swp -tags -bin/coquelicot/coquelicot -bin/coquelicot/dummy/root_storage/* diff --git a/api/controller/coquelicot.v1/Draft.md b/api/controller/coquelicot.v1/Draft.md deleted file mode 100644 index bb9bf6303..000000000 --- a/api/controller/coquelicot.v1/Draft.md +++ /dev/null @@ -1,142 +0,0 @@ -# Draft API - -## Response - -Single file: -``` -{ - "directory": "/image/2014/6f/w015i", - "type": "image", - "versions": { - "original": { - "filename": "original-15h1.png", - "height": 60, - "mime": "image/png", - "url": "/image/2014/6f/w015i/original-15h1.png", - "size": 3464, - "width": 53 - }, - "pic": { - "filename": "pic-15h1.png", - "height": 90, - "mime": "image/png", - "url": "/image/2014/6f/w015i/pic-15h1.png", - "size": 7648, - "width": 120 - } - } -} -``` - -Multiple files: -``` -{ - "files": [ - {...}, - {...} - ] -} -``` - -## Binary upload - -``` -POST /files HTTP/1.1 -Content-Length: 21744 -Accept: application/json -Content-Disposition: attachment; filename="pic.jpg" - -...bytes... -``` - -## Multipart - -``` -POST /files HTTP/1.1 -Content-Length: 21929 -Accept: application/json -Content-Type: multipart/form-data; boundary=----5XhQf4IXV9Q26uHM - -------5XhQf4IXV9Q26uHM -Content-Disposition: form-data; name="files[]"; filename="pic.jpg" -Content-Type: image/jpeg - -...bytes... -``` - -## Chunked multipart - -First request create temporary file -``` -POST /files HTTP/1.1 -Content-Length: 25185 -Content-Range: bytes 0-24999/52097 -Content-Disposition: attachment; filename="kino.jpg" -Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryAD3u12ABYZTJiIy3 - -------WebKitFormBoundaryAD3u12ABYZTJiIy3 -Content-Disposition: form-data; name="files[]"; filename="kino.jpg" -Content-Type: image/jpeg - -...bytes... -------WebKitFormBoundaryAD3u12ABYZTJiIy3-- -``` - -Second request write chunk to exists temporary file -``` -POST /files HTTP/1.1 -Content-Length: 25185 -Content-Range: bytes 25000-49999/52097 -Content-Disposition: attachment; filename="kino.jpg" -Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryvbE2anvAQyF3PWZS - -------WebKitFormBoundaryvbE2anvAQyF3PWZS -Content-Disposition: form-data; name="files[]"; filename="kino.jpg" -Content-Type: image/jpeg - -...bytes... -------WebKitFormBoundaryvbE2anvAQyF3PWZS-- -``` - -Last request write chunk to exists temporary file, complete upload, create attachment. -``` -POST /files HTTP/1.1 -Content-Length: 2282 -Content-Range: bytes 50000-52096/52097 -Content-Disposition: attachment; filename="kino.jpg" -Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryrHBafxSExXodxlnL - -------WebKitFormBoundaryrHBafxSExXodxlnL -Content-Disposition: form-data; name="files[]"; filename="kino.jpg" -Content-Type: image/jpeg - -...bytes... -------WebKitFormBoundaryrHBafxSExXodxlnL-- -``` - -## Chunked binary - -``` -POST /files HTTP/1.1 -Content-Length: 10240 -Content-Range: bytes 0-10239/36431 -Accept: application/json -Content-Disposition: attachment; filename="pic.jpg" -Content-Type: image/jpeg - -...bytes... -``` - -## Check chunked upload progress - -``` -PUT /files/some_url HTTP/1.1 -Content-Length: 0 -Content-Range: bytes */2000000 -``` - -``` -HTTP/1.1 308 Resume Incomplete -Content-Length: 0 -Range: 0-42 -``` diff --git a/api/controller/coquelicot.v1/LICENSE b/api/controller/coquelicot.v1/LICENSE deleted file mode 100644 index dfc90d7d1..000000000 --- a/api/controller/coquelicot.v1/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Zaur Abasmirzoev - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - diff --git a/api/controller/coquelicot.v1/README.md b/api/controller/coquelicot.v1/README.md deleted file mode 100644 index 7bc54c044..000000000 --- a/api/controller/coquelicot.v1/README.md +++ /dev/null @@ -1,73 +0,0 @@ -## Coquelicot - -Coquelicot is an easy to use server-side upload service written in Go. - -It is compatible with the [jQuery-File-Upload](https://github.com/blueimp/jQuery-File-Upload) -widget and supports chunked and resumable file upload. - -Using Coquelicot, you can add upload functionality to your project -very easily. Just download and unzip the Coquelicot binary distribution -for your OS and configure the jQuery-File-Upload widget. - -![logo](http://go-tsunami.com/assets/images/coquelicotLogo.jpg) - -### Server Setup - -You can use a binary release or get the project if you have a working Go installation. - -#### Binary Release - -Grab the latest [binary release](https://github.com/gotsunami/coquelicot/releases) for you system. Unzip it -and run - -``` -$ ./coquelicot -storage /tmp/files -host localhost:9073 -``` - -to store uploaded files into `/tmp/files` and make the application listen on the loopback interface port 9073 -(run `coquelicot.exe` on Windows). - -#### Source Release - -Grab the latest stable version with: - -``` -$ go get gopkg.in/gotsunami/coquelicot.v1 -``` - -See the [API documentation](http://gopkg.in/gotsunami/coquelicot.v1). - -### jQuery-File-Upload Setup (Client) - -The `fileupload` object needs the `xhrFields`, `maxChunkSize` and `add` fields to be defined. - -- `xhrFields`: enables sending of cross-domain cookies, which is required to properly handle chunks of data server-side -- `maxChunkSize`: enables uploading chunks of file -- `add`: overwrites the default `add` handler to support resuming file upload - -Download the [latest release](https://github.com/blueimp/jQuery-File-Upload/releases) of jQuery-File-Upload, -edit the `js/main.js` file in the distribution and make the `fileupload` initialization look like -(replacing the `localhost:9073` part with the name:port of your server running the `coquelicot` program): - -``` -$('#fileupload').fileupload({ - // Send cross-domain cookies - xhrFields: {withCredentials: true}, - url: 'http://localhost:9073/files', - // Chunk size in bytes - maxChunkSize: 1000000, - // Enable file resume - add: function (e, data) { - var that = this; - $.ajax({ - url: 'http://localhost:9073/resume', - xhrFields: {withCredentials: true}, - data: {file: data.files[0].name} - }).done(function(result) { - var file = result.file; - data.uploadedBytes = file && file.size; - $.blueimp.fileupload.prototype.options.add.call(that, e, data); - }); - } -}); -``` diff --git a/api/controller/coquelicot.v1/attachment_test.go b/api/controller/coquelicot.v1/attachment_test.go deleted file mode 100644 index 7cdd54470..000000000 --- a/api/controller/coquelicot.v1/attachment_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package coquelicot - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestCreateAttachment(t *testing.T) { - assert := assert.New(t) - - ofile := originalImageFile() - storage := dummy + "/root_storage" - converts := map[string]string{"original": "", "thumbnail": "120x80"} - - attachment, err := create(storage, ofile, converts, false) - assert.Nil(err) - // Convert option not set - assert.Equal(len(attachment.Versions), 1) - - data := attachment.ToJson() - assert.Equal(data["type"], "image") -} - -func originalImageFile() *originalFile { - return &originalFile{ - BaseMime: "image", - Filepath: dummy + "/32509211_news_bigpic.jpg", - Filename: "32509211_news_bigpic.jpg", - } -} - -func originalPdfFile() *originalFile { - return &originalFile{ - BaseMime: "application", - Filepath: dummy + "/Learning-Go-latest.pdf", - Filename: "Learning-Go-latest.pdf", - } -} diff --git a/api/controller/coquelicot.v1/bin/coquelicot/README.md b/api/controller/coquelicot.v1/bin/coquelicot/README.md deleted file mode 100644 index af254d2d4..000000000 --- a/api/controller/coquelicot.v1/bin/coquelicot/README.md +++ /dev/null @@ -1,2 +0,0 @@ - -Build it with `./build.sh` which includes a version number (from git). diff --git a/api/controller/coquelicot.v1/bin/coquelicot/build.sh b/api/controller/coquelicot.v1/bin/coquelicot/build.sh deleted file mode 100755 index 428324722..000000000 --- a/api/controller/coquelicot.v1/bin/coquelicot/build.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh - -# Writes a version file with the latest git commit id -# and any tag associated with it. - -COMMIT=$(git log --format="%h" -n 1) -TAG=$(git describe --all --exact-match $COMMIT) - -cat > version.go << EOF -package main - -const ( - appVersion = "$COMMIT $TAG" -) -EOF - -go build && rm -f version.go diff --git a/api/controller/coquelicot.v1/bin/coquelicot/config.go b/api/controller/coquelicot.v1/bin/coquelicot/config.go deleted file mode 100644 index bd99d6aac..000000000 --- a/api/controller/coquelicot.v1/bin/coquelicot/config.go +++ /dev/null @@ -1,13 +0,0 @@ -package main - -import "flag" - -var ( - // Root for storage - storage = flag.String("storage", "./dummy/root_storage", "Root for storage") - - // Host and port falco server - host = flag.String("host", "localhost:9073", "host:port for pavo server") - version = flag.Bool("version", false, "App version") - convert = flag.Bool("convert", false, "Use ImageMagick convert to create a thumbnail image") -) diff --git a/api/controller/coquelicot.v1/bin/coquelicot/dummy/32509211_news_bigpic.jpg b/api/controller/coquelicot.v1/bin/coquelicot/dummy/32509211_news_bigpic.jpg deleted file mode 100644 index 8700fa703..000000000 Binary files a/api/controller/coquelicot.v1/bin/coquelicot/dummy/32509211_news_bigpic.jpg and /dev/null differ diff --git a/api/controller/coquelicot.v1/bin/coquelicot/dummy/bin-data b/api/controller/coquelicot.v1/bin/coquelicot/dummy/bin-data deleted file mode 100644 index 7d868d54c..000000000 Binary files a/api/controller/coquelicot.v1/bin/coquelicot/dummy/bin-data and /dev/null differ diff --git a/api/controller/coquelicot.v1/bin/coquelicot/dummy/kino.jpg b/api/controller/coquelicot.v1/bin/coquelicot/dummy/kino.jpg deleted file mode 100644 index 44bfbec1e..000000000 Binary files a/api/controller/coquelicot.v1/bin/coquelicot/dummy/kino.jpg and /dev/null differ diff --git a/api/controller/coquelicot.v1/bin/coquelicot/main.go b/api/controller/coquelicot.v1/bin/coquelicot/main.go deleted file mode 100644 index 9a541e32c..000000000 --- a/api/controller/coquelicot.v1/bin/coquelicot/main.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "log" - "net/http" - "os" - - "gopkg.in/gotsunami/coquelicot.v1" -) - -func main() { - flag.Parse() - if *version { - fmt.Printf("version: %s\n", appVersion) - return - } - - s := coquelicot.NewStorage(*storage) - s.Option(coquelicot.Convert(*convert)) - - logger := log.New(os.Stdout, "", log.LstdFlags) - - routes := map[string]http.HandlerFunc{ - "/files": s.UploadHandler, - "/resume": s.ResumeHandler, - } - for path, handler := range routes { - http.Handle(path, coquelicot.Adapt(http.HandlerFunc(handler), - coquelicot.CORSMiddleware(), - coquelicot.LogMiddleware(logger)), - ) - } - - log.Printf("Storage place in: %s", s.StorageDir()) - log.Printf("Start server on %s", *host) - log.Fatal(http.ListenAndServe(*host, nil)) -} diff --git a/api/controller/coquelicot.v1/bin/coquelicot/main_test.go b/api/controller/coquelicot.v1/bin/coquelicot/main_test.go deleted file mode 100644 index c52dbec7f..000000000 --- a/api/controller/coquelicot.v1/bin/coquelicot/main_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package main - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" -) - -func TestUploadBinary(t *testing.T) { - assert := assert.New(t) - - req, _ := http.NewRequest("POST", "/files", nil) - req.Header.Set("Content-Type", "application/octet-stream") - req.Header.Set("X-File", "./dummy/bin-data") - req.Header.Set("Content-Disposition", `attachment; filename="basta.png"`) - req.AddCookie(&http.Cookie{Name: "coquelicot", Value: "abcdef"}) - - r := gin.Default() - - r.POST("/files", CreateAttachment) - rw := httptest.NewRecorder() - - r.ServeHTTP(rw, req) - assert.Equal(http.StatusCreated, rw.Code) - - //var d map[string]interface{} - //json.Unmarshal(rw.Body.Bytes(), &d) - - //t.Logf("json decode: %+v", d) -} - -func TestGetConvertParams(t *testing.T) { - assert := assert.New(t) - req, _ := http.NewRequest("POST", `/files?converts={"pic":"120x90"}`, nil) - - convert, err := GetConvertParams(req) - - assert.Nil(err) - assert.Equal("120x90", convert["pic"]) -} diff --git a/api/controller/coquelicot.v1/bin/coquelicot/release.sh b/api/controller/coquelicot.v1/bin/coquelicot/release.sh deleted file mode 100755 index e00b836c6..000000000 --- a/api/controller/coquelicot.v1/bin/coquelicot/release.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/sh - -# Writes a version file with the latest git commit id -# and any tag associated with it. - -COMMIT=$(git log --format="%h" -n 1) -TAG=$(git describe --all --exact-match $COMMIT) -BIN=coquelicot - -cat > version.go << EOF -package main - -const ( - appVersion = "$COMMIT $TAG" -) -EOF - -echo "(use -a to build for all platforms (go >= 1.5)" - -ARCH=amd64 -OSLIST=$(uname -s|tr '[:upper:]' '[:lower:]') - -if [ "$1" = "-a" ]; then - OSLIST="linux windows freebsd darwin" -fi - -rm -f /tmp/$BIN*.zip - -RTAG=$(git tag|tail -1) -if [ -z "$RTAG" ]; then - RTAG=$COMMIT -fi - -for OS in $OSLIST; do - echo "Building for $OS ..." - TMP=/tmp/c$OS$ARCH - rm -rf $TMP - GOOS=$OS GOARCH=$ARCH go build -o $TMP/$BIN - if [ "$OS" = "windows" ]; then - mv $TMP/$BIN $TMP/$BIN.exe - fi - zip -j /tmp/$BIN-$RTAG-$OS-$ARCH.zip $TMP/$BIN* && rm -rf $TMP -done - -rm -f version.go diff --git a/api/controller/coquelicot.v1/coquelicot.go b/api/controller/coquelicot.v1/coquelicot.go deleted file mode 100644 index f8763de66..000000000 --- a/api/controller/coquelicot.v1/coquelicot.go +++ /dev/null @@ -1,18 +0,0 @@ -// Package coquelicot provides (chunked) file upload capability (with resume). -package coquelicot - -type Storage struct { - output string - verbosity int -} - -// FIXME: global for now -var makeThumbnail bool - -func (s *Storage) StorageDir() string { - return s.output -} - -func NewStorage(rootDir string) *Storage { - return &Storage{output: rootDir} -} diff --git a/api/controller/coquelicot.v1/dir_test.go b/api/controller/coquelicot.v1/dir_test.go deleted file mode 100644 index 7fa485486..000000000 --- a/api/controller/coquelicot.v1/dir_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package coquelicot - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestPrepareDir(t *testing.T) { - assert := assert.New(t) - root := "dummy/root_storage" - - dm, err := createDir(root, "image") - assert.Nil(err) - assert.Equal(root, dm.Root) - - dm, err = checkDir(root, "/image/2014/2a/q1b12") - assert.Nil(err) -} diff --git a/api/controller/coquelicot.v1/file_image_manager.go b/api/controller/coquelicot.v1/file_image_manager.go deleted file mode 100644 index f561ebc19..000000000 --- a/api/controller/coquelicot.v1/file_image_manager.go +++ /dev/null @@ -1,75 +0,0 @@ -package coquelicot - -import ( - "fmt" - "os/exec" -) - -type fileImageManager struct { - *fileDefaultManager - Width int - Height int - thumbnail bool // Add resized version with ImageMagick -} - -// Save version from original with convert command-line tool. -func (fim *fileImageManager) convert(src string, convert string) error { - if !fim.thumbnail { - // Raw copy - return fim.rawCopy(src, convert) - } - err := convertImage(src, fim.Filepath(), convert) - if err != nil { - return err - } - - fim.Width, fim.Height, fim.Size, err = identifyImageSizes(fim.Filepath()) - if err != nil { - return err - } - - return nil -} - -func (fim *fileImageManager) ToJson() map[string]interface{} { - return map[string]interface{}{ - "url": fim.Url(), - "filename": fim.Filename, - "size": fim.Size, - "width": fim.Width, - "height": fim.Height, - } - -} - -func convertImage(src, dest, convert string) error { - args := []string{src, "-strip"} - if convert != "" { - cv := []string{"-resize", convert + "^", "-gravity", "center", "-extent", convert} - args = append(args, cv...) - } - args = append(args, dest) - - out, err := exec.Command("convert", args...).CombinedOutput() - if err != nil { - return fmt.Errorf("Error move original: %s, %s", err, string(out)) - } - - return nil -} - -func identifyImageSizes(filepath string) (int, int, int64, error) { - cmd := exec.Command("identify", "-format", `"%w:%h:%b"`, filepath) - - out, err := cmd.CombinedOutput() - if err != nil { - return 0, 0, 0, fmt.Errorf("Identify Sizes: %s; detail: %s", err, string(out)) - } - - var w, h int - var s int64 - - fmt.Sscanf(string(out), `"%d:%d:%dB"`, &w, &h, &s) - - return w, h, s, nil -} diff --git a/api/controller/coquelicot.v1/file_manager.go b/api/controller/coquelicot.v1/file_manager.go deleted file mode 100644 index c08351fff..000000000 --- a/api/controller/coquelicot.v1/file_manager.go +++ /dev/null @@ -1,113 +0,0 @@ -package coquelicot - -import ( - "fmt" - "io" - "os" - "path/filepath" - "time" -) - -type fileManager interface { - convert(string, string) error - SetFilename(*originalFile) - ToJson() map[string]interface{} -} - -type fileBaseManager struct { - Dir *dirManager - Version string - Filename string -} - -// Return fileManager for given base mime and version. -func newFileManager(dm *dirManager, mime_base, version string) fileManager { - fbm := &fileBaseManager{Dir: dm, Version: version} - fdm := &fileDefaultManager{fileBaseManager: fbm} - switch mime_base { - case "image": - return &fileImageManager{fileDefaultManager: fdm, thumbnail: makeThumbnail} - } - return fdm -} - -func (fbm *fileBaseManager) SetFilename(file *originalFile) { - ext := filepath.Ext(file.Filename) - fbm.Filename = file.Filename[:len(file.Filename)-len(ext)] + "-" + fbm.Version + file.Ext() - if fbm.Version == "original" { - fbm.Filename = file.Filename - } -} - -func (fbm *fileBaseManager) Filepath() string { - return filepath.Join(fbm.Dir.Abs(), fbm.Filename) -} - -func (fbm *fileBaseManager) Url() string { - return filepath.Join(fbm.Dir.Path, fbm.Filename) -} - -// copyFile copies a file from src to dst. If src and dst files exist, and are -// the same, then return success. Otherwise copy the file contents from src to dst. -func (fbm *fileBaseManager) copyFile(src, dst string) error { - sfi, err := os.Stat(src) - if err != nil { - return err - } - if !sfi.Mode().IsRegular() { - // cannot copy non-regular files (e.g., directories, - // symlinks, devices, etc.) - // FIXME - return fmt.Errorf("CopyFile: non-regular source file %s (%q)", sfi.Name(), sfi.Mode().String()) - } - dfi, err := os.Stat(dst) - if err != nil { - if !os.IsNotExist(err) { - return err - } - } else { - if !(dfi.Mode().IsRegular()) { - return fmt.Errorf("CopyFile: non-regular destination file %s (%q)", dfi.Name(), dfi.Mode().String()) - } - if os.SameFile(sfi, dfi) { - return err - } - } - if err := fbm.copyFileContents(src, dst); err != nil { - return err - } - return nil -} - -// copyFileContents copies the contents of the file named src to the file named -// by dst. The file will be created if it does not already exist. If the -// destination file exists, all it's contents will be replaced by the contents -// of the source file. -func (fbm *fileBaseManager) copyFileContents(src, dst string) error { - var err error - in, err := os.Open(src) - if err != nil { - return err - } - defer in.Close() - out, err := os.Create(dst) - if err != nil { - return err - } - defer func() { - cerr := out.Close() - if err == nil { - err = cerr - } - }() - if _, err = io.Copy(out, in); err != nil { - return err - } - err = out.Sync() - return err -} - -func seconds() int64 { - t := time.Now() - return int64(t.Hour()*3600 + t.Minute()*60 + t.Second()) -} diff --git a/api/controller/coquelicot.v1/meta_test.go b/api/controller/coquelicot.v1/meta_test.go deleted file mode 100644 index ea006d44d..000000000 --- a/api/controller/coquelicot.v1/meta_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package coquelicot - -import ( - "net/http" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestparseMeta(t *testing.T) { - assert := assert.New(t) - - req, _ := http.NewRequest("POST", "/files", nil) - req.Header.Set("Content-Type", "multipart/form-data; boundary=----Zam1WUeLK7vBj4wN") - req.Header.Set("Content-Range", "bytes 512000-1023999/1141216") - req.Header.Set("Content-Disposition", `attachment; filename="picture.jpg"`) - req.AddCookie(&http.Cookie{Name: "coquelicot", Value: "abcdef"}) - - meta, err := parseMeta(req) - assert.Nil(err) - - assert.Equal(meta.MediaType, "multipart/form-data") - assert.Equal(meta.Boundary, "----Zam1WUeLK7vBj4wN") - - assert.Equal(meta.Range.Start, int64(512000)) - assert.Equal(meta.Range.End, int64(1023999)) - assert.Equal(meta.Range.Size, int64(1141216)) - - assert.Equal(meta.Filename, "picture.jpg") - - assert.Equal(meta.UploadSid, "abcdef") -} diff --git a/api/controller/coquelicot.v1/middleware.go b/api/controller/coquelicot.v1/middleware.go deleted file mode 100644 index 534e62398..000000000 --- a/api/controller/coquelicot.v1/middleware.go +++ /dev/null @@ -1,50 +0,0 @@ -package coquelicot - -import ( - "log" - "net/http" -) - -type Adapter func(http.Handler) http.Handler - -func Adapt(h http.Handler, adapters ...Adapter) http.Handler { - for k := len(adapters) - 1; k >= 0; k-- { - h = adapters[k](h) - } - return h -} - -func CORSMiddleware() Adapter { - return func(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, PATCH, DELETE") - w.Header().Set("Access-Control-Allow-Headers", - "Content-Type, Content-Length, Accept-Encoding, Content-Range, Content-Disposition, Authorization") - // Since we need to support cross-domain cookies, we must support XHR requests - // with credentials, so the Access-Control-Allow-Credentials header is required - // and Access-Control-Allow-Origin cannot be equal to "*" but reply with the same Origin. - // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS. - w.Header().Set("Access-Control-Allow-Credentials", "true") - w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) - - if r.Method == "OPTIONS" { - return - } - - h.ServeHTTP(w, r) - }) - } -} - -func LogMiddleware(logger *log.Logger) Adapter { - return func(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - h.ServeHTTP(w, r) - path := r.URL.Path - if len(r.URL.RawQuery) > 0 { - path += "?" + r.URL.RawQuery - } - logger.Printf("%s %s [%s]\n", r.Method, path, r.RemoteAddr) - }) - } -} diff --git a/api/controller/coquelicot.v1/options.go b/api/controller/coquelicot.v1/options.go deleted file mode 100644 index 575fb4813..000000000 --- a/api/controller/coquelicot.v1/options.go +++ /dev/null @@ -1,23 +0,0 @@ -package coquelicot - -type option func(*Storage) - -func (s *Storage) Option(opts ...option) { - for _, opt := range opts { - opt(s) - } -} - -// Verbosity sets verbosity level (1 to 3). -func Verbosity(level int) option { - return func(s *Storage) { - s.verbosity = level - } -} - -// Convert generates an image thumbnail using ImageMagick. -func Convert(b bool) option { - return func(s *Storage) { - makeThumbnail = b - } -} diff --git a/api/controller/coquelicot.v1/upload.go b/api/controller/coquelicot.v1/upload.go deleted file mode 100644 index 90ca98776..000000000 --- a/api/controller/coquelicot.v1/upload.go +++ /dev/null @@ -1,231 +0,0 @@ -package coquelicot - -import ( - "crypto/md5" - "encoding/hex" - "errors" - "io" - "io/ioutil" - "mime/multipart" - "net/http" - "os" - "path/filepath" - "strings" -) - -// Error incomplete returned by uploader when loaded non-last chunk. -var incomplete = errors.New("incomplete") - -// Structure describes the state of the original file. -type originalFile struct { - BaseMime string - Filepath string - Filename string - Size int64 -} - -func (ofile *originalFile) Ext() string { - return strings.ToLower(filepath.Ext(ofile.Filename)) -} - -// Downloading files from the received request. -// The root directory of storage, storage, used to temporarily store chunks. -// Returns an array of the original files and error. -// If you load a portion of the file, chunk, it will be stored in err error incomplete, -// and in an array of a single file. File size will fit the current size. -func process(req *http.Request, storage string) ([]*originalFile, error) { - meta, err := parseMeta(req) - if err != nil { - return nil, err - } - - body, err := newBody(req.Header.Get("X-File"), req.Body) - if err != nil { - return nil, err - } - up := &uploader{Root: storage, Meta: meta, body: body} - - files, err := up.SaveFiles() - if err == incomplete { - return files, err - } - if err != nil { - return nil, err - } - - return files, nil -} - -// Upload manager. -type uploader struct { - Root string - Meta *meta - body *body -} - -// Function SaveFiles sequentially loads the original files or chunk's. -func (up *uploader) SaveFiles() ([]*originalFile, error) { - files := make([]*originalFile, 0) - for { - ofile, err := up.SaveFile() - if err == io.EOF { - break - } - - if err == incomplete { - files = append(files, ofile) - return files, err - } - - if err != nil { - return nil, err - } - - files = append(files, ofile) - } - - return files, nil -} - -// Function loads one or download the original file chunk. -// Asks for the starting position in the body of the request to read the next file. -// Asks for a temporary file. -// Writes data from the request body into a temporary file. -// Specifies the size of the resulting temporary file. -// If the query specified header Content-Range, -// and the size of the resulting file does not match, it returns an error incomplete. -// Otherwise, defines the basic mime type, and returns the original file. -func (up *uploader) SaveFile() (*originalFile, error) { - body, filename, err := up.Reader() - if err != nil { - return nil, err - } - - temp_file, err := up.tempFile() - if err != nil { - return nil, err - } - defer temp_file.Close() - - if err = up.Write(temp_file, body); err != nil { - return nil, err - } - - fi, err := temp_file.Stat() - if err != nil { - return nil, err - } - - ofile := &originalFile{Filename: filename, Filepath: temp_file.Name(), Size: fi.Size()} - - if up.Meta.Range != nil && ofile.Size != up.Meta.Range.Size { - return ofile, incomplete - } - - ofile.BaseMime, err = identifyMime(ofile.Filepath) - if err != nil { - return nil, err - } - - return ofile, nil -} - -// Returns the reader to read the file or chunk of request body and the original file name. -// If the request header Content-Type is multipart/form-data, returns the next copy part. -// If all of part read the case of binary loading read the request body, an error is returned io.EOF. -func (up *uploader) Reader() (io.Reader, string, error) { - if up.Meta.MediaType == "multipart/form-data" { - if up.body.MR == nil { - up.body.MR = multipart.NewReader(up.body.body, up.Meta.Boundary) - } - for { - part, err := up.body.MR.NextPart() - if err != nil { - return nil, "", err - } - if part.FormName() == "files[]" { - return part, part.FileName(), nil - } - } - } - - if !up.body.Available { - return nil, "", io.EOF - } - - up.body.Available = false - - return up.body.body, up.Meta.Filename, nil -} - -// Returns a temporary file to download the file or resume chunk. -func (up *uploader) tempFile() (*os.File, error) { - if up.Meta.Range == nil { - return tempFile() - } - return tempFileChunks(up.Meta.Range.Start, up.Root, up.Meta.UploadSid, up.Meta.Filename) -} - -// Returns the newly created temporary file. -func tempFile() (*os.File, error) { - return ioutil.TempFile(os.TempDir(), "coquelicot") -} - -// Returns a temporary file to download chunk. -// To calculate a unique file name used cookie named coquelicot and the original file name. -// File located in the directory chunks storage root directory. -// Before returning the file pointer is shifted by the value of offset, -// in a situation where the pieces are loaded from the second to the last. -func tempFileChunks(offset int64, storage, upload_sid, user_filename string) (*os.File, error) { - hasher := md5.New() - hasher.Write([]byte(upload_sid + user_filename)) - filename := hex.EncodeToString(hasher.Sum(nil)) - - path := filepath.Join(storage, "chunks") - - err := os.MkdirAll(path, 0755) - if err != nil { - return nil, err - } - - file, err := os.OpenFile(filepath.Join(path, filename), os.O_CREATE|os.O_WRONLY, 0664) - if err != nil { - return nil, err - } - - if _, err = file.Seek(offset, 0); err != nil { - return nil, err - } - - return file, nil -} - -// The function writes a temporary file value from reader. -func (up *uploader) Write(temp_file *os.File, body io.Reader) error { - var err error - if up.Meta.Range == nil { - _, err = io.Copy(temp_file, body) - } else { - chunk_size := up.Meta.Range.End - up.Meta.Range.Start + 1 - _, err = io.CopyN(temp_file, body, chunk_size) - } - return err -} - -// identifyMine gets base mime type. -func identifyMime(file string) (string, error) { - f, err := os.Open(file) - if err != nil { - return "", err - } - defer f.Close() - // DetectContentType reads at most the first 512 bytes - buf := make([]byte, 512) - _, err = f.Read(buf) - if err != nil { - return "", err - } - mime := strings.Split(http.DetectContentType(buf), "/")[0] - - return mime, nil -} diff --git a/api/controller/coquelicot.v1/upload_test.go b/api/controller/coquelicot.v1/upload_test.go deleted file mode 100644 index a3d337007..000000000 --- a/api/controller/coquelicot.v1/upload_test.go +++ /dev/null @@ -1,129 +0,0 @@ -package coquelicot - -import ( - "bytes" - "fmt" - "io" - "mime/multipart" - "net/http" - "os" - "path/filepath" - "testing" - - "code.google.com/p/go-uuid/uuid" - - "github.com/stretchr/testify/assert" -) - -const dummy = "bin/coquelicot/dummy" - -func TestUploadMultipart(t *testing.T) { - assert := assert.New(t) - - var body bytes.Buffer - mw := multipart.NewWriter(&body) - - if err := writeMPbody(dummy+"/32509211_news_bigpic.jpg", mw); err != nil { - assert.Error(err) - } - if err := writeMPbody(dummy+"/kino.jpg", mw); err != nil { - assert.Error(err) - } - - mw.Close() - - req, _ := http.NewRequest("POST", "/files", &body) - req.Header.Set("Content-Type", mw.FormDataContentType()) - req.AddCookie(&http.Cookie{Name: "coquelicot", Value: "abcdef"}) - - files, err := process(req, dummy+"/root_storage") - assert.Nil(err) - assert.Equal("kino.jpg", files[1].Filename) - assert.Equal("image", files[1].BaseMime) - -} -func TestUploadBinary(t *testing.T) { - assert := assert.New(t) - - req, _ := http.NewRequest("POST", "/files", nil) - req.Header.Set("Content-Type", "application/octet-stream") - req.Header.Set("X-File", dummy+"/bin-data") - req.Header.Set("Content-Disposition", `attachment; filename="basta.png"`) - req.AddCookie(&http.Cookie{Name: "coquelicot", Value: "abcdef"}) - - files, err := process(req, dummy+"/root_storage") - assert.Nil(err) - assert.Equal("basta.png", files[0].Filename) - assert.Equal("image", files[0].BaseMime) - -} - -func TestUploadChunked(t *testing.T) { - assert := assert.New(t) - storage := dummy + "/root_storage" - fname := dummy + "/kino.jpg" - f, _ := os.Open(fname) - defer f.Close() - - cookie := &http.Cookie{Name: "coquelicot", Value: uuid.New()} - - req := createChunkRequest(f, 0, 24999) - req.AddCookie(cookie) - files, err := process(req, storage) - assert.Equal(incomplete, err) - assert.Equal(25000, int(files[0].Size)) - - req = createChunkRequest(f, 25000, 49999) - req.AddCookie(cookie) - files, err = process(req, storage) - assert.Equal(incomplete, err) - assert.Equal(50000, int(files[0].Size)) - - req = createChunkRequest(f, 50000, 52096) - req.AddCookie(cookie) - files, err = process(req, storage) - assert.Nil(err) - assert.Equal(52097, int(files[0].Size)) - assert.Equal("kino.jpg", files[0].Filename) -} - -func createChunkRequest(f *os.File, start int64, end int64) *http.Request { - var body bytes.Buffer - mw := multipart.NewWriter(&body) - fi, _ := f.Stat() - fw, _ := mw.CreateFormFile("files[]", fi.Name()) - - io.CopyN(fw, f, end-start+1) - mw.Close() - - req, _ := http.NewRequest("POST", "/files", &body) - req.Header.Set("Content-Type", mw.FormDataContentType()) - req.Header.Set("Content-Disposition", `attachment; filename="`+fi.Name()+`"`) - req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, fi.Size())) - - return req -} - -func TestTempFileChunks(t *testing.T) { - assert := assert.New(t) - - file, err := tempFileChunks(0, dummy+"/root_storage", "abcdef", "kino.jpg") - assert.Nil(err) - assert.NotNil(file) -} - -func writeMPbody(fname string, mw *multipart.Writer) error { - fw, _ := mw.CreateFormFile("files[]", filepath.Base(fname)) - f, err := os.Open(fname) - if err != nil { - return err - } - defer f.Close() - - _, err = io.Copy(fw, f) - if err != nil { - return err - } - - return nil -} diff --git a/api/controller/coquelicot.v1/web.go b/api/controller/coquelicot.v1/web.go deleted file mode 100644 index f37855028..000000000 --- a/api/controller/coquelicot.v1/web.go +++ /dev/null @@ -1,144 +0,0 @@ -package coquelicot - -import ( - "crypto/md5" - "encoding/hex" - "encoding/json" - "fmt" - "log" - "net/http" - "os" - "path" - "time" - - // "github.com/gin-gonic/gin" - "github.com/pborman/uuid" - httputil "github.com/goodrain/rainbond/util/http" -) - -type H map[string]interface{} - -func toJSON(w http.ResponseWriter, code int, obj interface{}) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(code) - b, err := json.Marshal(obj) - if err != nil { - log.Println("error:", err.Error()) - return - } - if _, err := w.Write(b); err != nil { - log.Println("error:", err.Error()) - return - } -} - -// ResumeHandler allows resuming a file upload. -//func (s *Storage) ResumeHandler(c *gin.Context) { -func (s *Storage) ResumeHandler(w http.ResponseWriter, r *http.Request) { - status := http.StatusOK - filename := r.URL.Query().Get("file") - - cookie, _ := r.Cookie("coquelicot") - offset := int64(0) - - if cookie != nil { - hasher := md5.New() - hasher.Write([]byte(cookie.Value + filename)) - chunkname := hex.EncodeToString(hasher.Sum(nil)) - fi, err := os.Stat(path.Join(s.output, "chunks", chunkname)) - if err != nil { - if !os.IsNotExist(err) { - toJSON(w, http.StatusInternalServerError, H{ - "status": http.StatusText(http.StatusInternalServerError), - "error": fmt.Sprintf("Resume error: %q", err.Error()), - }) - return - } - } else { - offset = fi.Size() - } - } - - toJSON(w, status, H{"status": http.StatusText(status), "file": H{"size": offset}}) -} - -// UploadHandler is the endpoint for uploading and storing files. -//func (s *Storage) UploadHandler(c *gin.Context) { -func (s *Storage) UploadHandler(w http.ResponseWriter, r *http.Request) { - - converts, err := getConvertParams(r) - if err != nil { - toJSON(w, http.StatusBadRequest, H{ - "status": "error", - "error": fmt.Sprintf("Query params: %s", err), - }) - return - } - converts["original"] = "" - - // File upload cookie so we can keep track of chunks. - cookie, _ := r.Cookie("coquelicot") - if cookie == nil { - cookie = &http.Cookie{ - Name: "coquelicot", - Value: uuid.New(), - Expires: time.Now().Add(2 * 24 * time.Hour), - Path: "/", - } - r.AddCookie(cookie) - http.SetCookie(w, cookie) - } - - // Performs the processing of writing data into chunk files. - files, err := process(r, s.StorageDir()) - - if err == incomplete { - toJSON(w, http.StatusOK, H{ - "status": http.StatusText(http.StatusOK), - "file": H{"size": files[0].Size}, - }) - return - } - if err != nil { - toJSON(w, http.StatusBadRequest, H{ - "status": http.StatusText(http.StatusBadRequest), - "error": fmt.Sprintf("Upload error: %q", err.Error()), - }) - return - } - - data := make([]map[string]interface{}, 0) - - for _, ofile := range files { - println("1",ofile.Filename,"2",ofile.Filepath,"3",ofile.Size,"4",ofile.BaseMime) - // true to delete final chunk - _, err := create(s.StorageDir(), ofile, converts, true) - if err != nil { - data = append(data, map[string]interface{}{ - "name": ofile.Filename, - "size": ofile.Size, - "error": err.Error(), - }) - httputil.ReturnError(r,w,500,err.Error()) - } - } - httputil.ReturnSuccess(r, w, nil) -} - -// Get parameters for convert from Request query string -func getConvertParams(req *http.Request) (map[string]string, error) { - raw_converts := req.URL.Query().Get("converts") - - if raw_converts == "" { - raw_converts = "{}" - } - - convert := make(map[string]string) - - err := json.Unmarshal([]byte(raw_converts), &convert) - if err != nil { - return nil, err - } - - return convert, nil -} diff --git a/api/controller/coquelicot.v1/attachment.go b/api/controller/upload/attachment.go similarity index 61% rename from api/controller/coquelicot.v1/attachment.go rename to api/controller/upload/attachment.go index bf5a70803..29802b03e 100644 --- a/api/controller/coquelicot.v1/attachment.go +++ b/api/controller/upload/attachment.go @@ -1,4 +1,4 @@ -package coquelicot +package upload import ( "os" @@ -11,9 +11,7 @@ type attachment struct { Versions map[string]fileManager } -// Function receive root directory, original file, convertion parameters. -// Return attachment saved. The final chunk is deleted if delChunk is true. -func create(storage string, ofile *originalFile, converts map[string]string, delChunk bool) (*attachment, error) { +func create(storage string, ofile *originalFile, delChunk bool) (*attachment, error) { dm, err := createDir(storage, ofile.BaseMime) if err != nil { return nil, err @@ -25,12 +23,9 @@ func create(storage string, ofile *originalFile, converts map[string]string, del Versions: make(map[string]fileManager), } - if ofile.BaseMime == "image" { - converts["thumbnail"] = "120x90" - } makeVersion := func(a *attachment, version, convert string) error { - fm, err := at.createVersion(version, convert) + fm, err := at.createVersion(version) if err != nil { return err } @@ -42,12 +37,6 @@ func create(storage string, ofile *originalFile, converts map[string]string, del return nil, err } - if makeThumbnail { - if err := makeVersion(at, "thumbnail", converts["thumbnail"]); err != nil { - return nil, err - } - } - if delChunk { return at, os.Remove(at.originalFile.Filepath) } @@ -55,11 +44,11 @@ func create(storage string, ofile *originalFile, converts map[string]string, del } // Directly save single version and return fileManager. -func (attachment *attachment) createVersion(version string, convert string) (fileManager, error) { - fm := newFileManager(attachment.Dir, attachment.originalFile.BaseMime, version) +func (attachment *attachment) createVersion(version string) (fileManager, error) { + fm := newFileManager(attachment.Dir, version) fm.SetFilename(attachment.originalFile) - if err := fm.convert(attachment.originalFile.Filepath, convert); err != nil { + if err := fm.convert(attachment.originalFile.Filepath); err != nil { return nil, err } diff --git a/api/controller/coquelicot.v1/body.go b/api/controller/upload/body.go similarity index 62% rename from api/controller/coquelicot.v1/body.go rename to api/controller/upload/body.go index 38b7bf1a9..139491441 100644 --- a/api/controller/coquelicot.v1/body.go +++ b/api/controller/upload/body.go @@ -1,7 +1,6 @@ -package coquelicot +package upload import ( - "bufio" "io" "mime/multipart" "os" @@ -16,17 +15,8 @@ type body struct { } // Check exists body in xfile and return body. -func newBody(xfile string, req_body io.Reader) (*body, error) { - if xfile == "" { +func newBody(req_body io.Reader) (*body, error) { return &body{body: req_body, Available: true}, nil - } - - fh, err := os.Open(xfile) - if err != nil { - return nil, err - } - - return &body{XFile: fh, body: bufio.NewReader(fh), Available: true}, nil } // Close filehandler of body if XFile exists. diff --git a/api/controller/coquelicot.v1/dir.go b/api/controller/upload/dir.go similarity index 70% rename from api/controller/coquelicot.v1/dir.go rename to api/controller/upload/dir.go index 9bbe1bc0f..5b5269953 100644 --- a/api/controller/coquelicot.v1/dir.go +++ b/api/controller/upload/dir.go @@ -1,10 +1,6 @@ -package coquelicot +package upload import ( - "crypto/sha1" - "errors" - "fmt" - "io" "math/rand" "os" "path/filepath" @@ -30,17 +26,6 @@ func createDir(root, mime string) (*dirManager, error) { return dm, nil } -// Check path and return dirManager. -func checkDir(root, path string) (*dirManager, error) { - dm := newDirManager(root) - - if m, _ := filepath.Match("/[a-z]*/[0-9]*/[0-9a-z]*/[0-9a-z]*", path); m != true { - return nil, errors.New("dir: path does not match the pattern") - } - dm.Path = path - - return dm, nil -} // newDirManager returns a new dirManager given a root. func newDirManager(root string) *dirManager { @@ -59,9 +44,6 @@ func (dm *dirManager) create() error { // Generate path given mime and date. func (dm *dirManager) CalcPath(mime string) { - h := sha1.New() - io.WriteString(h, fmt.Sprintf("%d", time.Now().UnixNano())) - //dm.Path = fmt.Sprintf("/%x", h.Sum(nil)) dm.Path = "" } diff --git a/api/controller/coquelicot.v1/file_default_manager.go b/api/controller/upload/file_default_manager.go similarity index 71% rename from api/controller/coquelicot.v1/file_default_manager.go rename to api/controller/upload/file_default_manager.go index 1a5e93227..48ce4f130 100644 --- a/api/controller/coquelicot.v1/file_default_manager.go +++ b/api/controller/upload/file_default_manager.go @@ -1,4 +1,4 @@ -package coquelicot +package upload import ( "os" @@ -9,8 +9,8 @@ type fileDefaultManager struct { Size int64 } -func (fdm *fileDefaultManager) convert(src string, convert string) error { - return fdm.rawCopy(src, convert) +func (fdm *fileDefaultManager) convert(src string) error { + return fdm.rawCopy(src) } func (fdm *fileDefaultManager) ToJson() map[string]interface{} { @@ -21,7 +21,7 @@ func (fdm *fileDefaultManager) ToJson() map[string]interface{} { } } -func (fdm *fileDefaultManager) rawCopy(src, convert string) error { +func (fdm *fileDefaultManager) rawCopy(src string) error { if err := fdm.copyFile(src, fdm.Filepath()); err != nil { return err } diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/file_manager.go b/api/controller/upload/file_manager.go similarity index 85% rename from vendor/gopkg.in/gotsunami/coquelicot.v1/file_manager.go rename to api/controller/upload/file_manager.go index c08351fff..eef5321eb 100644 --- a/vendor/gopkg.in/gotsunami/coquelicot.v1/file_manager.go +++ b/api/controller/upload/file_manager.go @@ -1,4 +1,4 @@ -package coquelicot +package upload import ( "fmt" @@ -9,7 +9,7 @@ import ( ) type fileManager interface { - convert(string, string) error + convert(string) error SetFilename(*originalFile) ToJson() map[string]interface{} } @@ -21,22 +21,14 @@ type fileBaseManager struct { } // Return fileManager for given base mime and version. -func newFileManager(dm *dirManager, mime_base, version string) fileManager { +func newFileManager(dm *dirManager, version string) fileManager { fbm := &fileBaseManager{Dir: dm, Version: version} fdm := &fileDefaultManager{fileBaseManager: fbm} - switch mime_base { - case "image": - return &fileImageManager{fileDefaultManager: fdm, thumbnail: makeThumbnail} - } return fdm } func (fbm *fileBaseManager) SetFilename(file *originalFile) { - ext := filepath.Ext(file.Filename) - fbm.Filename = file.Filename[:len(file.Filename)-len(ext)] + "-" + fbm.Version + file.Ext() - if fbm.Version == "original" { fbm.Filename = file.Filename - } } func (fbm *fileBaseManager) Filepath() string { diff --git a/api/controller/coquelicot.v1/meta.go b/api/controller/upload/meta.go similarity index 66% rename from api/controller/coquelicot.v1/meta.go rename to api/controller/upload/meta.go index 106f57d34..43dd02b20 100644 --- a/api/controller/coquelicot.v1/meta.go +++ b/api/controller/upload/meta.go @@ -1,8 +1,7 @@ -package coquelicot +package upload import ( "errors" - "fmt" "mime" "net/http" ) @@ -11,15 +10,7 @@ import ( type meta struct { MediaType string Boundary string - Range *dataRange Filename string - UploadSid string -} - -type dataRange struct { - Start int64 - End int64 - Size int64 } // Parse request headers and make Meta. @@ -30,22 +21,10 @@ func parseMeta(req *http.Request) (*meta, error) { return nil, err } - if err := m.parseContentRange(req.Header.Get("Content-Range")); err != nil { - return nil, err - } - if err := m.parseContentDisposition(req.Header.Get("Content-Disposition")); err != nil { return nil, err } - cookie, err := req.Cookie("coquelicot") - if err != nil { - return nil, err - } - if cookie != nil { - m.UploadSid = cookie.Value - } - return m, nil } @@ -75,22 +54,6 @@ func (m *meta) parseContentType(ct string) error { return nil } -func (m *meta) parseContentRange(cr string) error { - if cr == "" { - return nil - } - - var start, end, size int64 - - _, err := fmt.Sscanf(cr, "bytes %d-%d/%d", &start, &end, &size) - if err != nil { - return err - } - - m.Range = &dataRange{Start: start, End: end, Size: size} - - return nil -} func (m *meta) parseContentDisposition(cd string) error { if cd == "" { diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/upload.go b/api/controller/upload/upload.go similarity index 84% rename from vendor/gopkg.in/gotsunami/coquelicot.v1/upload.go rename to api/controller/upload/upload.go index 90ca98776..407f4293a 100644 --- a/vendor/gopkg.in/gotsunami/coquelicot.v1/upload.go +++ b/api/controller/upload/upload.go @@ -1,4 +1,4 @@ -package coquelicot +package upload import ( "crypto/md5" @@ -28,18 +28,14 @@ func (ofile *originalFile) Ext() string { return strings.ToLower(filepath.Ext(ofile.Filename)) } -// Downloading files from the received request. -// The root directory of storage, storage, used to temporarily store chunks. -// Returns an array of the original files and error. -// If you load a portion of the file, chunk, it will be stored in err error incomplete, -// and in an array of a single file. File size will fit the current size. + func process(req *http.Request, storage string) ([]*originalFile, error) { meta, err := parseMeta(req) if err != nil { return nil, err } - body, err := newBody(req.Header.Get("X-File"), req.Body) + body, err := newBody(req.Body) if err != nil { return nil, err } @@ -71,12 +67,6 @@ func (up *uploader) SaveFiles() ([]*originalFile, error) { if err == io.EOF { break } - - if err == incomplete { - files = append(files, ofile) - return files, err - } - if err != nil { return nil, err } @@ -118,10 +108,6 @@ func (up *uploader) SaveFile() (*originalFile, error) { ofile := &originalFile{Filename: filename, Filepath: temp_file.Name(), Size: fi.Size()} - if up.Meta.Range != nil && ofile.Size != up.Meta.Range.Size { - return ofile, incomplete - } - ofile.BaseMime, err = identifyMime(ofile.Filepath) if err != nil { return nil, err @@ -160,10 +146,7 @@ func (up *uploader) Reader() (io.Reader, string, error) { // Returns a temporary file to download the file or resume chunk. func (up *uploader) tempFile() (*os.File, error) { - if up.Meta.Range == nil { - return tempFile() - } - return tempFileChunks(up.Meta.Range.Start, up.Root, up.Meta.UploadSid, up.Meta.Filename) + return tempFile() } // Returns the newly created temporary file. @@ -203,12 +186,7 @@ func tempFileChunks(offset int64, storage, upload_sid, user_filename string) (*o // The function writes a temporary file value from reader. func (up *uploader) Write(temp_file *os.File, body io.Reader) error { var err error - if up.Meta.Range == nil { - _, err = io.Copy(temp_file, body) - } else { - chunk_size := up.Meta.Range.End - up.Meta.Range.Start + 1 - _, err = io.CopyN(temp_file, body, chunk_size) - } + _, err = io.Copy(temp_file, body) return err } diff --git a/api/controller/upload/web.go b/api/controller/upload/web.go new file mode 100644 index 000000000..55e73f781 --- /dev/null +++ b/api/controller/upload/web.go @@ -0,0 +1,48 @@ +package upload + +import ( + "net/http" + httputil "github.com/goodrain/rainbond/util/http" +) + +type Storage struct { + output string + verbosity int +} + +func (s *Storage) StorageDir() string { + return s.output +} + +func NewStorage(rootDir string) *Storage { + return &Storage{output: rootDir} +} + + +// UploadHandler is the endpoint for uploading and storing files. +func (s *Storage) UploadHandler(w http.ResponseWriter, r *http.Request) { + + // Performs the processing of writing data into chunk files. + files, err := process(r, s.StorageDir()) + + if err == incomplete { + httputil.ReturnSuccess(r, w, nil) + return + } + if err != nil { + httputil.ReturnError(r, w, 500, err.Error()) + return + } + + data := make([]map[string]interface{}, 0) + + for _, file := range files { + attachment, err := create(s.StorageDir(), file, true) + if err != nil { + httputil.ReturnError(r, w, 500, err.Error()) + return + } + data = append(data, attachment.ToJson()) + } + httputil.ReturnSuccess(r, w, data) +} diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/.gitignore b/vendor/gopkg.in/gotsunami/coquelicot.v1/.gitignore deleted file mode 100644 index 065c2e0a8..000000000 --- a/vendor/gopkg.in/gotsunami/coquelicot.v1/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -*.swp -tags -bin/coquelicot/coquelicot -bin/coquelicot/dummy/root_storage/* diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/Draft.md b/vendor/gopkg.in/gotsunami/coquelicot.v1/Draft.md deleted file mode 100644 index bb9bf6303..000000000 --- a/vendor/gopkg.in/gotsunami/coquelicot.v1/Draft.md +++ /dev/null @@ -1,142 +0,0 @@ -# Draft API - -## Response - -Single file: -``` -{ - "directory": "/image/2014/6f/w015i", - "type": "image", - "versions": { - "original": { - "filename": "original-15h1.png", - "height": 60, - "mime": "image/png", - "url": "/image/2014/6f/w015i/original-15h1.png", - "size": 3464, - "width": 53 - }, - "pic": { - "filename": "pic-15h1.png", - "height": 90, - "mime": "image/png", - "url": "/image/2014/6f/w015i/pic-15h1.png", - "size": 7648, - "width": 120 - } - } -} -``` - -Multiple files: -``` -{ - "files": [ - {...}, - {...} - ] -} -``` - -## Binary upload - -``` -POST /files HTTP/1.1 -Content-Length: 21744 -Accept: application/json -Content-Disposition: attachment; filename="pic.jpg" - -...bytes... -``` - -## Multipart - -``` -POST /files HTTP/1.1 -Content-Length: 21929 -Accept: application/json -Content-Type: multipart/form-data; boundary=----5XhQf4IXV9Q26uHM - -------5XhQf4IXV9Q26uHM -Content-Disposition: form-data; name="files[]"; filename="pic.jpg" -Content-Type: image/jpeg - -...bytes... -``` - -## Chunked multipart - -First request create temporary file -``` -POST /files HTTP/1.1 -Content-Length: 25185 -Content-Range: bytes 0-24999/52097 -Content-Disposition: attachment; filename="kino.jpg" -Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryAD3u12ABYZTJiIy3 - -------WebKitFormBoundaryAD3u12ABYZTJiIy3 -Content-Disposition: form-data; name="files[]"; filename="kino.jpg" -Content-Type: image/jpeg - -...bytes... -------WebKitFormBoundaryAD3u12ABYZTJiIy3-- -``` - -Second request write chunk to exists temporary file -``` -POST /files HTTP/1.1 -Content-Length: 25185 -Content-Range: bytes 25000-49999/52097 -Content-Disposition: attachment; filename="kino.jpg" -Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryvbE2anvAQyF3PWZS - -------WebKitFormBoundaryvbE2anvAQyF3PWZS -Content-Disposition: form-data; name="files[]"; filename="kino.jpg" -Content-Type: image/jpeg - -...bytes... -------WebKitFormBoundaryvbE2anvAQyF3PWZS-- -``` - -Last request write chunk to exists temporary file, complete upload, create attachment. -``` -POST /files HTTP/1.1 -Content-Length: 2282 -Content-Range: bytes 50000-52096/52097 -Content-Disposition: attachment; filename="kino.jpg" -Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryrHBafxSExXodxlnL - -------WebKitFormBoundaryrHBafxSExXodxlnL -Content-Disposition: form-data; name="files[]"; filename="kino.jpg" -Content-Type: image/jpeg - -...bytes... -------WebKitFormBoundaryrHBafxSExXodxlnL-- -``` - -## Chunked binary - -``` -POST /files HTTP/1.1 -Content-Length: 10240 -Content-Range: bytes 0-10239/36431 -Accept: application/json -Content-Disposition: attachment; filename="pic.jpg" -Content-Type: image/jpeg - -...bytes... -``` - -## Check chunked upload progress - -``` -PUT /files/some_url HTTP/1.1 -Content-Length: 0 -Content-Range: bytes */2000000 -``` - -``` -HTTP/1.1 308 Resume Incomplete -Content-Length: 0 -Range: 0-42 -``` diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/LICENSE b/vendor/gopkg.in/gotsunami/coquelicot.v1/LICENSE deleted file mode 100644 index dfc90d7d1..000000000 --- a/vendor/gopkg.in/gotsunami/coquelicot.v1/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Zaur Abasmirzoev - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/README.md b/vendor/gopkg.in/gotsunami/coquelicot.v1/README.md deleted file mode 100644 index 7bc54c044..000000000 --- a/vendor/gopkg.in/gotsunami/coquelicot.v1/README.md +++ /dev/null @@ -1,73 +0,0 @@ -## Coquelicot - -Coquelicot is an easy to use server-side upload service written in Go. - -It is compatible with the [jQuery-File-Upload](https://github.com/blueimp/jQuery-File-Upload) -widget and supports chunked and resumable file upload. - -Using Coquelicot, you can add upload functionality to your project -very easily. Just download and unzip the Coquelicot binary distribution -for your OS and configure the jQuery-File-Upload widget. - -![logo](http://go-tsunami.com/assets/images/coquelicotLogo.jpg) - -### Server Setup - -You can use a binary release or get the project if you have a working Go installation. - -#### Binary Release - -Grab the latest [binary release](https://github.com/gotsunami/coquelicot/releases) for you system. Unzip it -and run - -``` -$ ./coquelicot -storage /tmp/files -host localhost:9073 -``` - -to store uploaded files into `/tmp/files` and make the application listen on the loopback interface port 9073 -(run `coquelicot.exe` on Windows). - -#### Source Release - -Grab the latest stable version with: - -``` -$ go get gopkg.in/gotsunami/coquelicot.v1 -``` - -See the [API documentation](http://gopkg.in/gotsunami/coquelicot.v1). - -### jQuery-File-Upload Setup (Client) - -The `fileupload` object needs the `xhrFields`, `maxChunkSize` and `add` fields to be defined. - -- `xhrFields`: enables sending of cross-domain cookies, which is required to properly handle chunks of data server-side -- `maxChunkSize`: enables uploading chunks of file -- `add`: overwrites the default `add` handler to support resuming file upload - -Download the [latest release](https://github.com/blueimp/jQuery-File-Upload/releases) of jQuery-File-Upload, -edit the `js/main.js` file in the distribution and make the `fileupload` initialization look like -(replacing the `localhost:9073` part with the name:port of your server running the `coquelicot` program): - -``` -$('#fileupload').fileupload({ - // Send cross-domain cookies - xhrFields: {withCredentials: true}, - url: 'http://localhost:9073/files', - // Chunk size in bytes - maxChunkSize: 1000000, - // Enable file resume - add: function (e, data) { - var that = this; - $.ajax({ - url: 'http://localhost:9073/resume', - xhrFields: {withCredentials: true}, - data: {file: data.files[0].name} - }).done(function(result) { - var file = result.file; - data.uploadedBytes = file && file.size; - $.blueimp.fileupload.prototype.options.add.call(that, e, data); - }); - } -}); -``` diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/attachment.go b/vendor/gopkg.in/gotsunami/coquelicot.v1/attachment.go deleted file mode 100644 index bf5a70803..000000000 --- a/vendor/gopkg.in/gotsunami/coquelicot.v1/attachment.go +++ /dev/null @@ -1,81 +0,0 @@ -package coquelicot - -import ( - "os" -) - -// attachment contain info about directory, base mime type and all files saved. -type attachment struct { - originalFile *originalFile - Dir *dirManager - Versions map[string]fileManager -} - -// Function receive root directory, original file, convertion parameters. -// Return attachment saved. The final chunk is deleted if delChunk is true. -func create(storage string, ofile *originalFile, converts map[string]string, delChunk bool) (*attachment, error) { - dm, err := createDir(storage, ofile.BaseMime) - if err != nil { - return nil, err - } - - at := &attachment{ - originalFile: ofile, - Dir: dm, - Versions: make(map[string]fileManager), - } - - if ofile.BaseMime == "image" { - converts["thumbnail"] = "120x90" - } - - makeVersion := func(a *attachment, version, convert string) error { - fm, err := at.createVersion(version, convert) - if err != nil { - return err - } - at.Versions[version] = fm - return nil - } - - if err := makeVersion(at, "original", ""); err != nil { - return nil, err - } - - if makeThumbnail { - if err := makeVersion(at, "thumbnail", converts["thumbnail"]); err != nil { - return nil, err - } - } - - if delChunk { - return at, os.Remove(at.originalFile.Filepath) - } - return at, nil -} - -// Directly save single version and return fileManager. -func (attachment *attachment) createVersion(version string, convert string) (fileManager, error) { - fm := newFileManager(attachment.Dir, attachment.originalFile.BaseMime, version) - fm.SetFilename(attachment.originalFile) - - if err := fm.convert(attachment.originalFile.Filepath, convert); err != nil { - return nil, err - } - - return fm, nil -} - -func (attachment *attachment) ToJson() map[string]interface{} { - data := make(map[string]interface{}) - data["type"] = attachment.originalFile.BaseMime - data["dir"] = attachment.Dir.Path - data["name"] = attachment.originalFile.Filename - versions := make(map[string]interface{}) - for version, fm := range attachment.Versions { - versions[version] = fm.ToJson() - } - data["versions"] = versions - - return data -} diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/attachment_test.go b/vendor/gopkg.in/gotsunami/coquelicot.v1/attachment_test.go deleted file mode 100644 index 7cdd54470..000000000 --- a/vendor/gopkg.in/gotsunami/coquelicot.v1/attachment_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package coquelicot - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestCreateAttachment(t *testing.T) { - assert := assert.New(t) - - ofile := originalImageFile() - storage := dummy + "/root_storage" - converts := map[string]string{"original": "", "thumbnail": "120x80"} - - attachment, err := create(storage, ofile, converts, false) - assert.Nil(err) - // Convert option not set - assert.Equal(len(attachment.Versions), 1) - - data := attachment.ToJson() - assert.Equal(data["type"], "image") -} - -func originalImageFile() *originalFile { - return &originalFile{ - BaseMime: "image", - Filepath: dummy + "/32509211_news_bigpic.jpg", - Filename: "32509211_news_bigpic.jpg", - } -} - -func originalPdfFile() *originalFile { - return &originalFile{ - BaseMime: "application", - Filepath: dummy + "/Learning-Go-latest.pdf", - Filename: "Learning-Go-latest.pdf", - } -} diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/README.md b/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/README.md deleted file mode 100644 index af254d2d4..000000000 --- a/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/README.md +++ /dev/null @@ -1,2 +0,0 @@ - -Build it with `./build.sh` which includes a version number (from git). diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/build.sh b/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/build.sh deleted file mode 100755 index 428324722..000000000 --- a/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/build.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh - -# Writes a version file with the latest git commit id -# and any tag associated with it. - -COMMIT=$(git log --format="%h" -n 1) -TAG=$(git describe --all --exact-match $COMMIT) - -cat > version.go << EOF -package main - -const ( - appVersion = "$COMMIT $TAG" -) -EOF - -go build && rm -f version.go diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/config.go b/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/config.go deleted file mode 100644 index bd99d6aac..000000000 --- a/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/config.go +++ /dev/null @@ -1,13 +0,0 @@ -package main - -import "flag" - -var ( - // Root for storage - storage = flag.String("storage", "./dummy/root_storage", "Root for storage") - - // Host and port falco server - host = flag.String("host", "localhost:9073", "host:port for pavo server") - version = flag.Bool("version", false, "App version") - convert = flag.Bool("convert", false, "Use ImageMagick convert to create a thumbnail image") -) diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/dummy/32509211_news_bigpic.jpg b/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/dummy/32509211_news_bigpic.jpg deleted file mode 100644 index 8700fa703..000000000 Binary files a/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/dummy/32509211_news_bigpic.jpg and /dev/null differ diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/dummy/bin-data b/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/dummy/bin-data deleted file mode 100644 index 7d868d54c..000000000 Binary files a/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/dummy/bin-data and /dev/null differ diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/dummy/kino.jpg b/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/dummy/kino.jpg deleted file mode 100644 index 44bfbec1e..000000000 Binary files a/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/dummy/kino.jpg and /dev/null differ diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/main.go b/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/main.go deleted file mode 100644 index 9a541e32c..000000000 --- a/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/main.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "log" - "net/http" - "os" - - "gopkg.in/gotsunami/coquelicot.v1" -) - -func main() { - flag.Parse() - if *version { - fmt.Printf("version: %s\n", appVersion) - return - } - - s := coquelicot.NewStorage(*storage) - s.Option(coquelicot.Convert(*convert)) - - logger := log.New(os.Stdout, "", log.LstdFlags) - - routes := map[string]http.HandlerFunc{ - "/files": s.UploadHandler, - "/resume": s.ResumeHandler, - } - for path, handler := range routes { - http.Handle(path, coquelicot.Adapt(http.HandlerFunc(handler), - coquelicot.CORSMiddleware(), - coquelicot.LogMiddleware(logger)), - ) - } - - log.Printf("Storage place in: %s", s.StorageDir()) - log.Printf("Start server on %s", *host) - log.Fatal(http.ListenAndServe(*host, nil)) -} diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/main_test.go b/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/main_test.go deleted file mode 100644 index c52dbec7f..000000000 --- a/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/main_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package main - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" -) - -func TestUploadBinary(t *testing.T) { - assert := assert.New(t) - - req, _ := http.NewRequest("POST", "/files", nil) - req.Header.Set("Content-Type", "application/octet-stream") - req.Header.Set("X-File", "./dummy/bin-data") - req.Header.Set("Content-Disposition", `attachment; filename="basta.png"`) - req.AddCookie(&http.Cookie{Name: "coquelicot", Value: "abcdef"}) - - r := gin.Default() - - r.POST("/files", CreateAttachment) - rw := httptest.NewRecorder() - - r.ServeHTTP(rw, req) - assert.Equal(http.StatusCreated, rw.Code) - - //var d map[string]interface{} - //json.Unmarshal(rw.Body.Bytes(), &d) - - //t.Logf("json decode: %+v", d) -} - -func TestGetConvertParams(t *testing.T) { - assert := assert.New(t) - req, _ := http.NewRequest("POST", `/files?converts={"pic":"120x90"}`, nil) - - convert, err := GetConvertParams(req) - - assert.Nil(err) - assert.Equal("120x90", convert["pic"]) -} diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/release.sh b/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/release.sh deleted file mode 100755 index e00b836c6..000000000 --- a/vendor/gopkg.in/gotsunami/coquelicot.v1/bin/coquelicot/release.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/sh - -# Writes a version file with the latest git commit id -# and any tag associated with it. - -COMMIT=$(git log --format="%h" -n 1) -TAG=$(git describe --all --exact-match $COMMIT) -BIN=coquelicot - -cat > version.go << EOF -package main - -const ( - appVersion = "$COMMIT $TAG" -) -EOF - -echo "(use -a to build for all platforms (go >= 1.5)" - -ARCH=amd64 -OSLIST=$(uname -s|tr '[:upper:]' '[:lower:]') - -if [ "$1" = "-a" ]; then - OSLIST="linux windows freebsd darwin" -fi - -rm -f /tmp/$BIN*.zip - -RTAG=$(git tag|tail -1) -if [ -z "$RTAG" ]; then - RTAG=$COMMIT -fi - -for OS in $OSLIST; do - echo "Building for $OS ..." - TMP=/tmp/c$OS$ARCH - rm -rf $TMP - GOOS=$OS GOARCH=$ARCH go build -o $TMP/$BIN - if [ "$OS" = "windows" ]; then - mv $TMP/$BIN $TMP/$BIN.exe - fi - zip -j /tmp/$BIN-$RTAG-$OS-$ARCH.zip $TMP/$BIN* && rm -rf $TMP -done - -rm -f version.go diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/body.go b/vendor/gopkg.in/gotsunami/coquelicot.v1/body.go deleted file mode 100644 index 38b7bf1a9..000000000 --- a/vendor/gopkg.in/gotsunami/coquelicot.v1/body.go +++ /dev/null @@ -1,39 +0,0 @@ -package coquelicot - -import ( - "bufio" - "io" - "mime/multipart" - "os" -) - -// Upload body info. -type body struct { - XFile *os.File - body io.Reader - MR *multipart.Reader - Available bool -} - -// Check exists body in xfile and return body. -func newBody(xfile string, req_body io.Reader) (*body, error) { - if xfile == "" { - return &body{body: req_body, Available: true}, nil - } - - fh, err := os.Open(xfile) - if err != nil { - return nil, err - } - - return &body{XFile: fh, body: bufio.NewReader(fh), Available: true}, nil -} - -// Close filehandler of body if XFile exists. -func (body *body) Close() error { - if body.XFile != nil { - return body.XFile.Close() - } - - return nil -} diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/coquelicot.go b/vendor/gopkg.in/gotsunami/coquelicot.v1/coquelicot.go deleted file mode 100644 index f8763de66..000000000 --- a/vendor/gopkg.in/gotsunami/coquelicot.v1/coquelicot.go +++ /dev/null @@ -1,18 +0,0 @@ -// Package coquelicot provides (chunked) file upload capability (with resume). -package coquelicot - -type Storage struct { - output string - verbosity int -} - -// FIXME: global for now -var makeThumbnail bool - -func (s *Storage) StorageDir() string { - return s.output -} - -func NewStorage(rootDir string) *Storage { - return &Storage{output: rootDir} -} diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/dir.go b/vendor/gopkg.in/gotsunami/coquelicot.v1/dir.go deleted file mode 100644 index c5032acca..000000000 --- a/vendor/gopkg.in/gotsunami/coquelicot.v1/dir.go +++ /dev/null @@ -1,76 +0,0 @@ -package coquelicot - -import ( - "crypto/sha1" - "errors" - "fmt" - "io" - "math/rand" - "os" - "path/filepath" - "strconv" - "time" -) - -// Directory mananger -type dirManager struct { - Root string - Path string -} - -// Prepare dirManager given root, mime. -func createDir(root, mime string) (*dirManager, error) { - dm := newDirManager(root) - - dm.CalcPath(mime) - if err := dm.create(); err != nil { - return nil, err - } - - return dm, nil -} - -// Check path and return dirManager. -func checkDir(root, path string) (*dirManager, error) { - dm := newDirManager(root) - - if m, _ := filepath.Match("/[a-z]*/[0-9]*/[0-9a-z]*/[0-9a-z]*", path); m != true { - return nil, errors.New("dir: path does not match the pattern") - } - dm.Path = path - - return dm, nil -} - -// newDirManager returns a new dirManager given a root. -func newDirManager(root string) *dirManager { - return &dirManager{Root: root} -} - -// Return absolute path for directory -func (dm *dirManager) Abs() string { - return filepath.Join(dm.Root, dm.Path) -} - -// Create directory obtained by concatenating the root and path. -func (dm *dirManager) create() error { - return os.MkdirAll(dm.Root+dm.Path, 0755) -} - -// Generate path given mime and date. -func (dm *dirManager) CalcPath(mime string) { - h := sha1.New() - io.WriteString(h, fmt.Sprintf("%d", time.Now().UnixNano())) - dm.Path = fmt.Sprintf("/%x", h.Sum(nil)) -} - -func yearDay(t time.Time) string { - return strconv.FormatInt(int64(t.YearDay()), 36) -} - -func containerName(t time.Time) string { - r := rand.New(rand.NewSource(time.Now().UnixNano())).Intn(1000) - seconds := t.Hour()*3600 + t.Minute()*60 + t.Second() - - return strconv.FormatInt(int64(seconds*1000+r), 36) -} diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/dir_test.go b/vendor/gopkg.in/gotsunami/coquelicot.v1/dir_test.go deleted file mode 100644 index 7fa485486..000000000 --- a/vendor/gopkg.in/gotsunami/coquelicot.v1/dir_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package coquelicot - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestPrepareDir(t *testing.T) { - assert := assert.New(t) - root := "dummy/root_storage" - - dm, err := createDir(root, "image") - assert.Nil(err) - assert.Equal(root, dm.Root) - - dm, err = checkDir(root, "/image/2014/2a/q1b12") - assert.Nil(err) -} diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/file_default_manager.go b/vendor/gopkg.in/gotsunami/coquelicot.v1/file_default_manager.go deleted file mode 100644 index 1a5e93227..000000000 --- a/vendor/gopkg.in/gotsunami/coquelicot.v1/file_default_manager.go +++ /dev/null @@ -1,40 +0,0 @@ -package coquelicot - -import ( - "os" -) - -type fileDefaultManager struct { - *fileBaseManager - Size int64 -} - -func (fdm *fileDefaultManager) convert(src string, convert string) error { - return fdm.rawCopy(src, convert) -} - -func (fdm *fileDefaultManager) ToJson() map[string]interface{} { - return map[string]interface{}{ - "url": fdm.Url(), - "filename": fdm.Filename, - "size": fdm.Size, - } -} - -func (fdm *fileDefaultManager) rawCopy(src, convert string) error { - if err := fdm.copyFile(src, fdm.Filepath()); err != nil { - return err - } - - f, err := os.Open(fdm.Filepath()) - if err != nil { - return err - } - fi, err := f.Stat() - if err != nil { - return err - } - fdm.Size = fi.Size() - - return nil -} diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/file_image_manager.go b/vendor/gopkg.in/gotsunami/coquelicot.v1/file_image_manager.go deleted file mode 100644 index f561ebc19..000000000 --- a/vendor/gopkg.in/gotsunami/coquelicot.v1/file_image_manager.go +++ /dev/null @@ -1,75 +0,0 @@ -package coquelicot - -import ( - "fmt" - "os/exec" -) - -type fileImageManager struct { - *fileDefaultManager - Width int - Height int - thumbnail bool // Add resized version with ImageMagick -} - -// Save version from original with convert command-line tool. -func (fim *fileImageManager) convert(src string, convert string) error { - if !fim.thumbnail { - // Raw copy - return fim.rawCopy(src, convert) - } - err := convertImage(src, fim.Filepath(), convert) - if err != nil { - return err - } - - fim.Width, fim.Height, fim.Size, err = identifyImageSizes(fim.Filepath()) - if err != nil { - return err - } - - return nil -} - -func (fim *fileImageManager) ToJson() map[string]interface{} { - return map[string]interface{}{ - "url": fim.Url(), - "filename": fim.Filename, - "size": fim.Size, - "width": fim.Width, - "height": fim.Height, - } - -} - -func convertImage(src, dest, convert string) error { - args := []string{src, "-strip"} - if convert != "" { - cv := []string{"-resize", convert + "^", "-gravity", "center", "-extent", convert} - args = append(args, cv...) - } - args = append(args, dest) - - out, err := exec.Command("convert", args...).CombinedOutput() - if err != nil { - return fmt.Errorf("Error move original: %s, %s", err, string(out)) - } - - return nil -} - -func identifyImageSizes(filepath string) (int, int, int64, error) { - cmd := exec.Command("identify", "-format", `"%w:%h:%b"`, filepath) - - out, err := cmd.CombinedOutput() - if err != nil { - return 0, 0, 0, fmt.Errorf("Identify Sizes: %s; detail: %s", err, string(out)) - } - - var w, h int - var s int64 - - fmt.Sscanf(string(out), `"%d:%d:%dB"`, &w, &h, &s) - - return w, h, s, nil -} diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/meta.go b/vendor/gopkg.in/gotsunami/coquelicot.v1/meta.go deleted file mode 100644 index 106f57d34..000000000 --- a/vendor/gopkg.in/gotsunami/coquelicot.v1/meta.go +++ /dev/null @@ -1,113 +0,0 @@ -package coquelicot - -import ( - "errors" - "fmt" - "mime" - "net/http" -) - -// Info about request headers -type meta struct { - MediaType string - Boundary string - Range *dataRange - Filename string - UploadSid string -} - -type dataRange struct { - Start int64 - End int64 - Size int64 -} - -// Parse request headers and make Meta. -func parseMeta(req *http.Request) (*meta, error) { - m := &meta{} - - if err := m.parseContentType(req.Header.Get("Content-Type")); err != nil { - return nil, err - } - - if err := m.parseContentRange(req.Header.Get("Content-Range")); err != nil { - return nil, err - } - - if err := m.parseContentDisposition(req.Header.Get("Content-Disposition")); err != nil { - return nil, err - } - - cookie, err := req.Cookie("coquelicot") - if err != nil { - return nil, err - } - if cookie != nil { - m.UploadSid = cookie.Value - } - - return m, nil -} - -func (m *meta) parseContentType(ct string) error { - if ct == "" { - m.MediaType = "application/octet-stream" - return nil - } - - mediatype, params, err := mime.ParseMediaType(ct) - if err != nil { - return err - } - - if mediatype == "multipart/form-data" { - boundary, ok := params["boundary"] - if !ok { - return errors.New("meta: boundary not defined") - } - - m.MediaType = mediatype - m.Boundary = boundary - } else { - m.MediaType = "application/octet-stream" - } - - return nil -} - -func (m *meta) parseContentRange(cr string) error { - if cr == "" { - return nil - } - - var start, end, size int64 - - _, err := fmt.Sscanf(cr, "bytes %d-%d/%d", &start, &end, &size) - if err != nil { - return err - } - - m.Range = &dataRange{Start: start, End: end, Size: size} - - return nil -} - -func (m *meta) parseContentDisposition(cd string) error { - if cd == "" { - return nil - } - - _, params, err := mime.ParseMediaType(cd) - if err != nil { - return err - } - - filename, ok := params["filename"] - if !ok { - return errors.New("meta: filename in Content-Disposition not defined") - } - - m.Filename = filename - - return nil -} diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/meta_test.go b/vendor/gopkg.in/gotsunami/coquelicot.v1/meta_test.go deleted file mode 100644 index ea006d44d..000000000 --- a/vendor/gopkg.in/gotsunami/coquelicot.v1/meta_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package coquelicot - -import ( - "net/http" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestparseMeta(t *testing.T) { - assert := assert.New(t) - - req, _ := http.NewRequest("POST", "/files", nil) - req.Header.Set("Content-Type", "multipart/form-data; boundary=----Zam1WUeLK7vBj4wN") - req.Header.Set("Content-Range", "bytes 512000-1023999/1141216") - req.Header.Set("Content-Disposition", `attachment; filename="picture.jpg"`) - req.AddCookie(&http.Cookie{Name: "coquelicot", Value: "abcdef"}) - - meta, err := parseMeta(req) - assert.Nil(err) - - assert.Equal(meta.MediaType, "multipart/form-data") - assert.Equal(meta.Boundary, "----Zam1WUeLK7vBj4wN") - - assert.Equal(meta.Range.Start, int64(512000)) - assert.Equal(meta.Range.End, int64(1023999)) - assert.Equal(meta.Range.Size, int64(1141216)) - - assert.Equal(meta.Filename, "picture.jpg") - - assert.Equal(meta.UploadSid, "abcdef") -} diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/middleware.go b/vendor/gopkg.in/gotsunami/coquelicot.v1/middleware.go deleted file mode 100644 index 534e62398..000000000 --- a/vendor/gopkg.in/gotsunami/coquelicot.v1/middleware.go +++ /dev/null @@ -1,50 +0,0 @@ -package coquelicot - -import ( - "log" - "net/http" -) - -type Adapter func(http.Handler) http.Handler - -func Adapt(h http.Handler, adapters ...Adapter) http.Handler { - for k := len(adapters) - 1; k >= 0; k-- { - h = adapters[k](h) - } - return h -} - -func CORSMiddleware() Adapter { - return func(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, PATCH, DELETE") - w.Header().Set("Access-Control-Allow-Headers", - "Content-Type, Content-Length, Accept-Encoding, Content-Range, Content-Disposition, Authorization") - // Since we need to support cross-domain cookies, we must support XHR requests - // with credentials, so the Access-Control-Allow-Credentials header is required - // and Access-Control-Allow-Origin cannot be equal to "*" but reply with the same Origin. - // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS. - w.Header().Set("Access-Control-Allow-Credentials", "true") - w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) - - if r.Method == "OPTIONS" { - return - } - - h.ServeHTTP(w, r) - }) - } -} - -func LogMiddleware(logger *log.Logger) Adapter { - return func(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - h.ServeHTTP(w, r) - path := r.URL.Path - if len(r.URL.RawQuery) > 0 { - path += "?" + r.URL.RawQuery - } - logger.Printf("%s %s [%s]\n", r.Method, path, r.RemoteAddr) - }) - } -} diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/options.go b/vendor/gopkg.in/gotsunami/coquelicot.v1/options.go deleted file mode 100644 index 575fb4813..000000000 --- a/vendor/gopkg.in/gotsunami/coquelicot.v1/options.go +++ /dev/null @@ -1,23 +0,0 @@ -package coquelicot - -type option func(*Storage) - -func (s *Storage) Option(opts ...option) { - for _, opt := range opts { - opt(s) - } -} - -// Verbosity sets verbosity level (1 to 3). -func Verbosity(level int) option { - return func(s *Storage) { - s.verbosity = level - } -} - -// Convert generates an image thumbnail using ImageMagick. -func Convert(b bool) option { - return func(s *Storage) { - makeThumbnail = b - } -} diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/upload_test.go b/vendor/gopkg.in/gotsunami/coquelicot.v1/upload_test.go deleted file mode 100644 index a3d337007..000000000 --- a/vendor/gopkg.in/gotsunami/coquelicot.v1/upload_test.go +++ /dev/null @@ -1,129 +0,0 @@ -package coquelicot - -import ( - "bytes" - "fmt" - "io" - "mime/multipart" - "net/http" - "os" - "path/filepath" - "testing" - - "code.google.com/p/go-uuid/uuid" - - "github.com/stretchr/testify/assert" -) - -const dummy = "bin/coquelicot/dummy" - -func TestUploadMultipart(t *testing.T) { - assert := assert.New(t) - - var body bytes.Buffer - mw := multipart.NewWriter(&body) - - if err := writeMPbody(dummy+"/32509211_news_bigpic.jpg", mw); err != nil { - assert.Error(err) - } - if err := writeMPbody(dummy+"/kino.jpg", mw); err != nil { - assert.Error(err) - } - - mw.Close() - - req, _ := http.NewRequest("POST", "/files", &body) - req.Header.Set("Content-Type", mw.FormDataContentType()) - req.AddCookie(&http.Cookie{Name: "coquelicot", Value: "abcdef"}) - - files, err := process(req, dummy+"/root_storage") - assert.Nil(err) - assert.Equal("kino.jpg", files[1].Filename) - assert.Equal("image", files[1].BaseMime) - -} -func TestUploadBinary(t *testing.T) { - assert := assert.New(t) - - req, _ := http.NewRequest("POST", "/files", nil) - req.Header.Set("Content-Type", "application/octet-stream") - req.Header.Set("X-File", dummy+"/bin-data") - req.Header.Set("Content-Disposition", `attachment; filename="basta.png"`) - req.AddCookie(&http.Cookie{Name: "coquelicot", Value: "abcdef"}) - - files, err := process(req, dummy+"/root_storage") - assert.Nil(err) - assert.Equal("basta.png", files[0].Filename) - assert.Equal("image", files[0].BaseMime) - -} - -func TestUploadChunked(t *testing.T) { - assert := assert.New(t) - storage := dummy + "/root_storage" - fname := dummy + "/kino.jpg" - f, _ := os.Open(fname) - defer f.Close() - - cookie := &http.Cookie{Name: "coquelicot", Value: uuid.New()} - - req := createChunkRequest(f, 0, 24999) - req.AddCookie(cookie) - files, err := process(req, storage) - assert.Equal(incomplete, err) - assert.Equal(25000, int(files[0].Size)) - - req = createChunkRequest(f, 25000, 49999) - req.AddCookie(cookie) - files, err = process(req, storage) - assert.Equal(incomplete, err) - assert.Equal(50000, int(files[0].Size)) - - req = createChunkRequest(f, 50000, 52096) - req.AddCookie(cookie) - files, err = process(req, storage) - assert.Nil(err) - assert.Equal(52097, int(files[0].Size)) - assert.Equal("kino.jpg", files[0].Filename) -} - -func createChunkRequest(f *os.File, start int64, end int64) *http.Request { - var body bytes.Buffer - mw := multipart.NewWriter(&body) - fi, _ := f.Stat() - fw, _ := mw.CreateFormFile("files[]", fi.Name()) - - io.CopyN(fw, f, end-start+1) - mw.Close() - - req, _ := http.NewRequest("POST", "/files", &body) - req.Header.Set("Content-Type", mw.FormDataContentType()) - req.Header.Set("Content-Disposition", `attachment; filename="`+fi.Name()+`"`) - req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, fi.Size())) - - return req -} - -func TestTempFileChunks(t *testing.T) { - assert := assert.New(t) - - file, err := tempFileChunks(0, dummy+"/root_storage", "abcdef", "kino.jpg") - assert.Nil(err) - assert.NotNil(file) -} - -func writeMPbody(fname string, mw *multipart.Writer) error { - fw, _ := mw.CreateFormFile("files[]", filepath.Base(fname)) - f, err := os.Open(fname) - if err != nil { - return err - } - defer f.Close() - - _, err = io.Copy(fw, f) - if err != nil { - return err - } - - return nil -} diff --git a/vendor/gopkg.in/gotsunami/coquelicot.v1/web.go b/vendor/gopkg.in/gotsunami/coquelicot.v1/web.go deleted file mode 100644 index edd8488ed..000000000 --- a/vendor/gopkg.in/gotsunami/coquelicot.v1/web.go +++ /dev/null @@ -1,157 +0,0 @@ -package coquelicot - -import ( - "crypto/md5" - "encoding/hex" - "encoding/json" - "fmt" - "log" - "net/http" - "os" - "path" - "time" - - // "github.com/gin-gonic/gin" - "github.com/pborman/uuid" -) - -type H map[string]interface{} - -func toJSON(w http.ResponseWriter, code int, obj interface{}) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(code) - b, err := json.Marshal(obj) - if err != nil { - log.Println("error:", err.Error()) - return - } - if _, err := w.Write(b); err != nil { - log.Println("error:", err.Error()) - return - } -} - -// ResumeHandler allows resuming a file upload. -//func (s *Storage) ResumeHandler(c *gin.Context) { -func (s *Storage) ResumeHandler(w http.ResponseWriter, r *http.Request) { - status := http.StatusOK - filename := r.URL.Query().Get("file") - - cookie, _ := r.Cookie("coquelicot") - offset := int64(0) - - if cookie != nil { - hasher := md5.New() - hasher.Write([]byte(cookie.Value + filename)) - chunkname := hex.EncodeToString(hasher.Sum(nil)) - fi, err := os.Stat(path.Join(s.output, "chunks", chunkname)) - if err != nil { - if !os.IsNotExist(err) { - toJSON(w, http.StatusInternalServerError, H{ - "status": http.StatusText(http.StatusInternalServerError), - "error": fmt.Sprintf("Resume error: %q", err.Error()), - }) - return - } - } else { - offset = fi.Size() - } - } - - toJSON(w, status, H{"status": http.StatusText(status), "file": H{"size": offset}}) -} - -// UploadHandler is the endpoint for uploading and storing files. -//func (s *Storage) UploadHandler(c *gin.Context) { -func (s *Storage) UploadHandler(w http.ResponseWriter, r *http.Request) { - if r.Method == "GET" { - status := http.StatusOK - // FIXME: nil content - toJSON(w, status, H{"status": http.StatusText(status), "files": nil}) - return - } - if r.Method != "POST" { - http.NotFound(w, r) - return - } - - converts, err := getConvertParams(r) - if err != nil { - toJSON(w, http.StatusBadRequest, H{ - "status": "error", - "error": fmt.Sprintf("Query params: %s", err), - }) - return - } - converts["original"] = "" - - // File upload cookie so we can keep track of chunks. - cookie, _ := r.Cookie("coquelicot") - if cookie == nil { - cookie = &http.Cookie{ - Name: "coquelicot", - Value: uuid.New(), - Expires: time.Now().Add(2 * 24 * time.Hour), - Path: "/", - } - r.AddCookie(cookie) - http.SetCookie(w, cookie) - } - - // Performs the processing of writing data into chunk files. - files, err := process(r, s.StorageDir()) - - if err == incomplete { - toJSON(w, http.StatusOK, H{ - "status": http.StatusText(http.StatusOK), - "file": H{"size": files[0].Size}, - }) - return - } - if err != nil { - toJSON(w, http.StatusBadRequest, H{ - "status": http.StatusText(http.StatusBadRequest), - "error": fmt.Sprintf("Upload error: %q", err.Error()), - }) - return - } - - data := make([]map[string]interface{}, 0) - // Expected status if no error - status := http.StatusCreated - - for _, ofile := range files { - println("1",ofile.Filename,"2",ofile.Filepath,"3",ofile.Size,"4",ofile.BaseMime) - // true to delete final chunk - attachment, err := create(s.StorageDir(), ofile, converts, true) - if err != nil { - data = append(data, map[string]interface{}{ - "name": ofile.Filename, - "size": ofile.Size, - "error": err.Error(), - }) - status = http.StatusInternalServerError - continue - } - data = append(data, attachment.ToJson()) - } - toJSON(w, status, H{"status": http.StatusText(status), "files": data}) -} - -// Get parameters for convert from Request query string -func getConvertParams(req *http.Request) (map[string]string, error) { - raw_converts := req.URL.Query().Get("converts") - - if raw_converts == "" { - raw_converts = "{}" - } - - convert := make(map[string]string) - - err := json.Unmarshal([]byte(raw_converts), &convert) - if err != nil { - return nil, err - } - - return convert, nil -}