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