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  }