Add support for copying a file
This commit is contained in:
parent
4318d9988a
commit
50d16a3dcd
18
http.go
18
http.go
|
@ -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)))
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user