github.com/kotovmak/go-admin@v1.1.1/modules/file/file.go (about)

     1  // Copyright 2019 GoAdmin Core Team. All rights reserved.
     2  // Use of this source code is governed by a Apache-2.0 style
     3  // license that can be found in the LICENSE file.
     4  
     5  package file
     6  
     7  import (
     8  	"fmt"
     9  	"io"
    10  	"mime/multipart"
    11  	"os"
    12  	"path"
    13  	"sync"
    14  
    15  	"github.com/kotovmak/go-admin/plugins/admin/modules"
    16  )
    17  
    18  // Uploader is a file uploader which contains the method Upload.
    19  type Uploader interface {
    20  	Upload(*multipart.Form) error
    21  }
    22  
    23  // UploaderGenerator is a function return an Uploader.
    24  type UploaderGenerator func() Uploader
    25  
    26  var uploaderList = map[string]UploaderGenerator{
    27  	"local": GetLocalFileUploader,
    28  }
    29  
    30  var upMu sync.Mutex
    31  
    32  // AddUploader makes a uploader generator available by the provided theme name.
    33  // If Add is called twice with the same name or if uploader is nil,
    34  // it panics.
    35  func AddUploader(name string, up UploaderGenerator) {
    36  	upMu.Lock()
    37  	defer upMu.Unlock()
    38  	if up == nil {
    39  		panic("uploader generator is nil")
    40  	}
    41  	if _, dup := uploaderList[name]; dup {
    42  		panic("add uploader generator twice " + name)
    43  	}
    44  	uploaderList[name] = up
    45  }
    46  
    47  // GetFileEngine return the Uploader of given name.
    48  func GetFileEngine(name string) Uploader {
    49  	if up, ok := uploaderList[name]; ok {
    50  		return up()
    51  	}
    52  	panic("wrong uploader name")
    53  }
    54  
    55  // UploadFun is a function to process the uploading logic.
    56  type UploadFun func(*multipart.FileHeader, string) (string, error)
    57  
    58  // Upload receive the return value of given UploadFun and put them into the form.
    59  func Upload(c UploadFun, form *multipart.Form) error {
    60  	var (
    61  		suffix   string
    62  		filename string
    63  	)
    64  
    65  	for k := range form.File {
    66  		for _, fileObj := range form.File[k] {
    67  			suffix = path.Ext(fileObj.Filename)
    68  			filename = modules.Uuid() + suffix
    69  
    70  			pathStr, err := c(fileObj, filename)
    71  
    72  			if err != nil {
    73  				return err
    74  			}
    75  
    76  			form.Value[k] = append(form.Value[k], pathStr)
    77  			form.Value[k+"_size"] = append(form.Value[k+"_size"], fmt.Sprintf("%d", fileObj.Size))
    78  		}
    79  	}
    80  
    81  	return nil
    82  }
    83  
    84  // SaveMultipartFile used in a local Uploader which help to save file in the local path.
    85  func SaveMultipartFile(fh *multipart.FileHeader, path string) error {
    86  	f, err := fh.Open()
    87  	if err != nil {
    88  		return err
    89  	}
    90  
    91  	if ff, ok := f.(*os.File); ok {
    92  		// Windows can't rename files that are opened.
    93  		if err := f.Close(); err != nil {
    94  			return err
    95  		}
    96  
    97  		// If renaming fails we try the normal copying method.
    98  		// Renaming could fail if the files are on different devices.
    99  		if os.Rename(ff.Name(), path) == nil {
   100  			return nil
   101  		}
   102  
   103  		// Reopen f for the code below.
   104  		f, err = fh.Open()
   105  		if err != nil {
   106  			return err
   107  		}
   108  	}
   109  
   110  	defer func() {
   111  		if err2 := f.Close(); err2 != nil {
   112  			err = err2
   113  		}
   114  	}()
   115  
   116  	ff, err := os.Create(path)
   117  	if err != nil {
   118  		return err
   119  	}
   120  
   121  	defer func() {
   122  		if err2 := ff.Close(); err2 != nil {
   123  			err = err2
   124  		}
   125  	}()
   126  	_, err = copyZeroAlloc(ff, f)
   127  	return err
   128  }
   129  
   130  func copyZeroAlloc(w io.Writer, r io.Reader) (int64, error) {
   131  	buf := copyBufPool.Get().([]byte)
   132  	n, err := io.CopyBuffer(w, r, buf)
   133  	copyBufPool.Put(buf)
   134  	return n, err
   135  }
   136  
   137  var copyBufPool = sync.Pool{
   138  	New: func() interface{} {
   139  		return make([]byte, 4096)
   140  	},
   141  }