123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- package upload
- import (
- "errors"
- "io"
- "log"
- "net/http"
- "os"
- "path/filepath"
- user "imuslab.com/arozos/mod/user"
- )
- type chunk struct {
- PartFilename string
- DestFilename string
- }
- /*
- //Usage:
- uploadedFilepaths, err := upload.StreamUploadToDisk(userinfo, w, r)
- if err != nil {
- sendErrorResponse(w, err.Error())
- return
- }
- //Set the ownership of file(s)
- quotaFulled := false
- for _, fileRealpath := range uploadedFilepaths {
- //Check for storage quota
- uploadFileSize := fs.GetFileSize(fileRealpath)
- if !userinfo.StorageQuota.HaveSpace(uploadFileSize) {
- //User storage quota fulled. Remove this file
- quotaFulled = true
- //Remove this file as this doesn't fit
- os.Remove(fileRealpath)
- } else {
- userinfo.SetOwnerOfFile(fileRealpath)
- }
- }
- if quotaFulled {
- sendErrorResponse(w, "Storage Quota Full")
- return
- }
- sendOK(w)
- return
- */
- func StreamUploadToDisk(userinfo *user.User, w http.ResponseWriter, r *http.Request) ([]string, error) {
- //Check if this userinfo is valid
- if userinfo == nil {
- return []string{}, errors.New("Invalid userinfo")
- }
- vpath, ok := r.URL.Query()["path"]
- if !ok || len(vpath) == 0 {
- return []string{}, errors.New("Invalid upload destination")
- }
- //Get the upload destination realpath
- realUploadPath, err := userinfo.VirtualPathToRealPath(vpath[0])
- if err != nil {
- //Return virtual path to real path translation error
- return []string{}, err
- }
- //Try to parse the FORM POST using multipart reader
- reader, err := r.MultipartReader()
- if err != nil {
- log.Println("Upload failed: " + err.Error())
- return []string{}, err
- }
- //Start write process
- uplaodedFiles := map[string]string{}
- for {
- part, err := reader.NextPart()
- if err == io.EOF {
- break
- } else if err != nil {
- //Connection lost when uploading. Remove the uploading file.
- clearFailedUploadChunks(uplaodedFiles)
- return []string{}, errors.New("Upload failed")
- }
- defer part.Close()
- //Check if this is file or other paramters
- if part.FileName() != "" {
- //This part is a part of a file. Write it to destination folder
- tmpFilepath := filepath.Join(realUploadPath, part.FileName()+".tmp")
- //Check if this part is uploaded before. If not but the .tmp file exists
- //This is from previous unsuccessful upload and it should be reoved
- _, ok := uplaodedFiles[part.FileName()]
- if !ok && fileExists(tmpFilepath) {
- //This chunk is first chunk of the file and the .tmp file already exists.
- //Remove it
- log.Println("Removing previous failed upload: ", tmpFilepath)
- os.Remove(tmpFilepath)
- }
- //Check if the uploading target folder exists. If not, create it
- if !fileExists(filepath.Dir(tmpFilepath)) {
- os.MkdirAll(filepath.Dir(tmpFilepath), 0755)
- }
- //Open the file and write to it using append mode
- d, err := os.OpenFile(tmpFilepath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0775)
- if err != nil {
- //Failed to create new file.
- clearFailedUploadChunks(uplaodedFiles)
- return []string{}, errors.New("Write to disk failed")
- }
- io.Copy(d, part)
- d.Close()
- //Record this file
- uplaodedFiles[part.FileName()] = tmpFilepath
- } else {
- //Unknown stuffs
- continue
- }
- }
- //Remove the .tmp extension for the file.
- uploadedFilepaths := []string{}
- for thisFilename, thisFilepath := range uplaodedFiles {
- thisFilename = filepath.Base(thisFilename)
- finalDestFilename := filepath.Join(filepath.Dir(thisFilepath), thisFilename)
- //Remove the .tmp from the upload by renaming it as the original name
- err = os.Rename(thisFilepath, finalDestFilename)
- if err != nil {
- clearFailedUploadChunks(uplaodedFiles)
- return []string{}, err
- }
- uploadedFilepaths = append(uploadedFilepaths, finalDestFilename)
- log.Println(userinfo.Username+" uploaded: ", filepath.Base(thisFilepath))
- }
- return uploadedFilepaths, nil
- }
- //This function remove all the chunks for failed file upload from disk
- func clearFailedUploadChunks(uplaodedFiles map[string]string) {
- for _, tmpFiles := range uplaodedFiles {
- os.Remove(tmpFiles)
- }
- }
|