github.com/rpdict/ponzu@v0.10.1-0.20190226054626-477f29d6bf5e/system/admin/upload/upload.go (about) 1 // Package upload provides a re-usable file upload and storage utility for Ponzu 2 // systems to handle multipart form data. 3 package upload 4 5 import ( 6 "fmt" 7 "io" 8 "log" 9 "mime/multipart" 10 "net/http" 11 "net/url" 12 "os" 13 "path/filepath" 14 "strconv" 15 "time" 16 17 "github.com/rpdict/ponzu/system/db" 18 "github.com/rpdict/ponzu/system/item" 19 ) 20 21 // StoreFiles stores file uploads at paths like /YYYY/MM/filename.ext 22 func StoreFiles(req *http.Request) (map[string]string, error) { 23 err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB 24 if err != nil { 25 return nil, err 26 } 27 28 ts := req.FormValue("timestamp") // timestamp in milliseconds since unix epoch 29 30 if ts == "" { 31 ts = fmt.Sprintf("%d", int64(time.Nanosecond)*time.Now().UnixNano()/int64(time.Millisecond)) // Unix() returns seconds since unix epoch 32 } 33 34 req.Form.Set("timestamp", ts) 35 36 // To use for FormValue name:urlPath 37 urlPaths := make(map[string]string) 38 39 // get or create upload directory to save files from request 40 pwd, err := os.Getwd() 41 if err != nil { 42 err := fmt.Errorf("Failed to locate current directory: %s", err) 43 return nil, err 44 } 45 46 i, err := strconv.ParseInt(ts, 10, 64) 47 if err != nil { 48 return nil, err 49 } 50 51 tm := time.Unix(int64(i/1000), int64(i%1000)) 52 53 urlPathPrefix := "api" 54 uploadDirName := "uploads" 55 56 uploadDir := filepath.Join(pwd, uploadDirName, fmt.Sprintf("%d", tm.Year()), fmt.Sprintf("%02d", tm.Month())) 57 err = os.MkdirAll(uploadDir, os.ModeDir|os.ModePerm) 58 if err != nil { 59 return nil, err 60 } 61 62 // loop over all files and save them to disk 63 for name, fds := range req.MultipartForm.File { 64 filename, err := item.NormalizeString(fds[0].Filename) 65 if err != nil { 66 return nil, err 67 } 68 69 src, err := fds[0].Open() 70 if err != nil { 71 err := fmt.Errorf("Couldn't open uploaded file: %s", err) 72 return nil, err 73 74 } 75 defer src.Close() 76 77 // check if file at path exists, if so, add timestamp to file 78 absPath := filepath.Join(uploadDir, filename) 79 80 if _, err := os.Stat(absPath); !os.IsNotExist(err) { 81 filename = fmt.Sprintf("%d-%s", time.Now().Unix(), filename) 82 absPath = filepath.Join(uploadDir, filename) 83 } 84 85 // save to disk (TODO: or check if S3 credentials exist, & save to cloud) 86 dst, err := os.Create(absPath) 87 if err != nil { 88 err := fmt.Errorf("Failed to create destination file for upload: %s", err) 89 return nil, err 90 } 91 92 // copy file from src to dst on disk 93 var size int64 94 if size, err = io.Copy(dst, src); err != nil { 95 err := fmt.Errorf("Failed to copy uploaded file to destination: %s", err) 96 return nil, err 97 } 98 99 // add name:urlPath to req.PostForm to be inserted into db 100 urlPath := fmt.Sprintf("/%s/%s/%d/%02d/%s", urlPathPrefix, uploadDirName, tm.Year(), tm.Month(), filename) 101 urlPaths[name] = urlPath 102 103 // add upload information to db 104 go storeFileInfo(size, filename, urlPath, fds) 105 } 106 107 return urlPaths, nil 108 } 109 110 func storeFileInfo(size int64, filename, urlPath string, fds []*multipart.FileHeader) { 111 data := url.Values{ 112 "name": []string{filename}, 113 "path": []string{urlPath}, 114 "content_type": []string{fds[0].Header.Get("Content-Type")}, 115 "content_length": []string{fmt.Sprintf("%d", size)}, 116 } 117 118 _, err := db.SetUpload("__uploads:-1", data) 119 if err != nil { 120 log.Println("Error saving file upload record to database:", err) 121 } 122 }