package upload import ( "errors" "io" "log" "net/http" "os" "path/filepath" user "imuslab.com/arozos/mod/user" ) type chunk struct { PartFilename string DestFilename string } 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) } }