Add support for copying a file

This commit is contained in:
Dane Everitt 2019-05-04 17:02:01 -07:00
parent 4318d9988a
commit 50d16a3dcd
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
2 changed files with 101 additions and 0 deletions

18
http.go
View File

@ -321,6 +321,23 @@ func (rt *Router) routeServerRenameFile(w http.ResponseWriter, r *http.Request,
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
} }
func (rt *Router) routeServerCopyFile(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
s := rt.Servers.Get(ps.ByName("server"))
defer r.Body.Close()
data := rt.ReaderToBytes(r.Body)
copyLocation, _ := jsonparser.GetString(data, "file_location")
if err := s.Filesystem.Copy(copyLocation); err != nil {
zap.S().Errorw("error copying file for server", zap.String("server", s.Uuid), zap.Error(err))
http.Error(w, "an error occurred while copying the file", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
}
func (rt *Router) ReaderToBytes(r io.Reader) []byte { func (rt *Router) ReaderToBytes(r io.Reader) []byte {
buf := bytes.Buffer{} buf := bytes.Buffer{}
buf.ReadFrom(r) buf.ReadFrom(r)
@ -339,6 +356,7 @@ func (rt *Router) ConfigureRouter() *httprouter.Router {
router.GET("/api/servers/:server/files/read/*path", rt.AuthenticateToken("s:files", rt.AuthenticateServer(rt.routeServerFileRead))) router.GET("/api/servers/:server/files/read/*path", rt.AuthenticateToken("s:files", rt.AuthenticateServer(rt.routeServerFileRead)))
router.GET("/api/servers/:server/files/list/*path", rt.AuthenticateToken("s:files", rt.AuthenticateServer(rt.routeServerFileList))) router.GET("/api/servers/:server/files/list/*path", rt.AuthenticateToken("s:files", rt.AuthenticateServer(rt.routeServerFileList)))
router.PUT("/api/servers/:server/files/rename", rt.AuthenticateToken("s:files", rt.AuthenticateServer(rt.routeServerRenameFile))) router.PUT("/api/servers/:server/files/rename", rt.AuthenticateToken("s:files", rt.AuthenticateServer(rt.routeServerRenameFile)))
router.POST("/api/servers/:server/files/copy", rt.AuthenticateToken("s:files", rt.AuthenticateServer(rt.routeServerCopyFile)))
router.POST("/api/servers/:server/files/create-directory", rt.AuthenticateToken("s:files", rt.AuthenticateServer(rt.routeServerCreateDirectory))) router.POST("/api/servers/:server/files/create-directory", rt.AuthenticateToken("s:files", rt.AuthenticateServer(rt.routeServerCreateDirectory)))
router.POST("/api/servers/:server/power", rt.AuthenticateToken("s:power", rt.AuthenticateServer(rt.routeServerPower))) router.POST("/api/servers/:server/power", rt.AuthenticateToken("s:power", rt.AuthenticateServer(rt.routeServerPower)))

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"github.com/gabriel-vasile/mimetype" "github.com/gabriel-vasile/mimetype"
"go.uber.org/zap" "go.uber.org/zap"
"io" "io"
@ -12,6 +13,7 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"sort" "sort"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -287,6 +289,87 @@ func (fs *Filesystem) Rename(from string, to string) error {
return os.Rename(cleanedFrom, cleanedTo) return os.Rename(cleanedFrom, cleanedTo)
} }
// Copies a given file to the same location and appends a suffix to the file to indicate that
// it has been copied.
//
// @todo need to get an exclusive lock on the file.
func (fs *Filesystem) Copy(p string) error {
cleaned, err := fs.SafePath(p)
if err != nil {
return err
}
if s, err := os.Stat(cleaned); (err != nil && os.IsNotExist(err)) || s.IsDir() || !s.Mode().IsRegular() {
// For now I think I am okay just returning a nil response if the thing
// we're trying to copy doesn't exist. Probably will want to come back and
// re-evaluate if this is a smart decision (I'm guessing not).
return nil
} else if err != nil {
return err
}
base := filepath.Base(cleaned)
relative := strings.TrimSuffix(strings.TrimPrefix(cleaned, fs.Path()), base)
extension := filepath.Ext(base)
name := strings.TrimSuffix(base, filepath.Ext(base))
// Begin looping up to 50 times to try and create a unique copy file name. This will take
// an input of "file.txt" and generate "file copy.txt". If that name is already taken, it will
// then try to write "file copy 2.txt" and so on, until reaching 50 loops. At that point we
// won't waste anymore time, just use the current timestamp and make that copy.
//
// Could probably make this more efficient by checking if there are any files matching the copy
// pattern, and trying to find the highest number and then incrementing it by one rather than
// looping endlessly.
var i int
copySuffix := " copy"
for i = 0; i < 51; i++ {
if i > 0 {
copySuffix = " copy " + strconv.Itoa(i)
}
tryName := fmt.Sprintf("%s%s%s", name, copySuffix, extension)
tryLocation, err := fs.SafePath(path.Join(relative, tryName))
if err != nil {
return err
}
// If the file exists, continue to the next loop, otherwise we're good to start a copy.
if _, err := os.Stat(tryLocation); err != nil && !os.IsNotExist(err) {
return err
} else if os.IsNotExist(err) {
break
}
if i == 50 {
copySuffix = "." + time.Now().Format(time.RFC3339)
}
}
finalPath, err := fs.SafePath(path.Join(relative, fmt.Sprintf("%s%s%s", name, copySuffix, extension)))
if err != nil {
return err
}
source, err := os.Open(cleaned)
if err != nil {
return err
}
defer source.Close()
dest, err := os.Create(finalPath)
if err != nil {
return err
}
defer dest.Close()
if _, err := io.Copy(dest, source); err != nil {
return err
}
return nil
}
// Lists the contents of a given directory and returns stat information about each // Lists the contents of a given directory and returns stat information about each
// file and folder within it. // file and folder within it.
func (fs *Filesystem) ListDirectory(p string) ([]*Stat, error) { func (fs *Filesystem) ListDirectory(p string) ([]*Stat, error) {