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  }