Add support for listing a directory's contents
This commit is contained in:
parent
91afa4d38e
commit
0ace25c117
2
go.mod
2
go.mod
|
@ -12,7 +12,7 @@ require (
|
|||
github.com/docker/docker v0.0.0-20180422163414-57142e89befe
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/docker/go-units v0.3.3 // indirect
|
||||
github.com/gabriel-vasile/mimetype v0.1.4 // indirect
|
||||
github.com/gabriel-vasile/mimetype v0.1.4
|
||||
github.com/gogo/protobuf v1.0.0 // indirect
|
||||
github.com/google/go-cmp v0.2.0 // indirect
|
||||
github.com/gotestyourself/gotestyourself v2.2.0+incompatible // indirect
|
||||
|
|
21
http.go
21
http.go
|
@ -232,6 +232,24 @@ func (rt *Router) routeServerFileRead(w http.ResponseWriter, r *http.Request, ps
|
|||
bufio.NewReader(f).WriteTo(w)
|
||||
}
|
||||
|
||||
// Lists the contents of a directory.
|
||||
func (rt *Router) routeServerFileList(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
s := rt.Servers.Get(ps.ByName("server"))
|
||||
|
||||
stats, err := s.Filesystem().ListDirectory(ps.ByName("path"))
|
||||
if os.IsNotExist(err) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
} else if err != nil {
|
||||
zap.S().Errorw("failed to list contents of directory", zap.String("server", s.Uuid), zap.String("path", ps.ByName("path")), zap.Error(err))
|
||||
|
||||
http.Error(w, "failed to list directory", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(stats)
|
||||
}
|
||||
|
||||
// Configures the router and all of the associated routes.
|
||||
func (rt *Router) ConfigureRouter() *httprouter.Router {
|
||||
router := httprouter.New()
|
||||
|
@ -240,7 +258,8 @@ func (rt *Router) ConfigureRouter() *httprouter.Router {
|
|||
router.GET("/api/servers", rt.AuthenticateToken("i:servers", rt.routeAllServers))
|
||||
router.GET("/api/servers/:server", rt.AuthenticateToken("s:view", rt.AuthenticateServer(rt.routeServer)))
|
||||
router.GET("/api/servers/:server/logs", rt.AuthenticateToken("s:logs", rt.AuthenticateServer(rt.routeServerLogs)))
|
||||
router.GET("/api/servers/:server/files/*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.POST("/api/servers/:server/power", rt.AuthenticateToken("s:power", rt.AuthenticateServer(rt.routeServerPower)))
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package server
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"go.uber.org/zap"
|
||||
|
@ -9,6 +10,7 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
@ -202,10 +204,34 @@ func (fs *Filesystem) DeleteFile(p string) error {
|
|||
|
||||
// Defines the stat struct object.
|
||||
type Stat struct {
|
||||
Info *os.FileInfo
|
||||
Info os.FileInfo
|
||||
Mimetype string
|
||||
}
|
||||
|
||||
func (s *Stat) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
Name string
|
||||
Created string
|
||||
Modified string
|
||||
Mode string
|
||||
Size int64
|
||||
Directory bool
|
||||
File bool
|
||||
Symlink bool
|
||||
Mime string
|
||||
}{
|
||||
Name: s.Info.Name(),
|
||||
Created: s.CTime().String(),
|
||||
Modified: s.Info.ModTime().String(),
|
||||
Mode: s.Info.Mode().String(),
|
||||
Size: s.Info.Size(),
|
||||
Directory: s.Info.IsDir(),
|
||||
File: !s.Info.IsDir(),
|
||||
Symlink: s.Info.Mode().Perm()&os.ModeSymlink != 0,
|
||||
Mime: s.Mimetype,
|
||||
})
|
||||
}
|
||||
|
||||
// Stats a file or folder and returns the base stat object from go along with the
|
||||
// MIME data that can be used for editing files.
|
||||
func (fs *Filesystem) Stat(p string) (*Stat, error) {
|
||||
|
@ -228,9 +254,66 @@ func (fs *Filesystem) Stat(p string) (*Stat, error) {
|
|||
}
|
||||
|
||||
st := &Stat{
|
||||
Info: &s,
|
||||
Info: s,
|
||||
Mimetype: m,
|
||||
}
|
||||
|
||||
return st, nil
|
||||
}
|
||||
|
||||
// Lists the contents of a given directory and returns stat information about each
|
||||
// file and folder within it.
|
||||
func (fs *Filesystem) ListDirectory(p string) ([]*Stat, error) {
|
||||
cleaned, err := fs.SafePath(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files, err := ioutil.ReadDir(cleaned)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var out []*Stat
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Iterate over all of the files and directories returned and perform an async process
|
||||
// to get the mime-type for them all.
|
||||
for _, file := range files {
|
||||
wg.Add(1)
|
||||
|
||||
go func(f os.FileInfo) {
|
||||
defer wg.Done()
|
||||
|
||||
var m = "inode/directory"
|
||||
if !f.IsDir() {
|
||||
m, _, _ = mimetype.DetectFile(filepath.Join(cleaned, f.Name()))
|
||||
}
|
||||
|
||||
out = append(out, &Stat{
|
||||
Info: f,
|
||||
Mimetype: m,
|
||||
})
|
||||
}(file)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Sort the output alphabetically to begin with since we've run the output
|
||||
// through an asynchronous process and the order is gonna be very random.
|
||||
sort.SliceStable(out, func(i, j int) bool {
|
||||
if out[i].Info.Name() == out[j].Info.Name() || out[i].Info.Name() < out[j].Info.Name() {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
// Then, sort it so that directories are listed first in the output. Everything
|
||||
// will continue to be alphabetized at this point.
|
||||
sort.SliceStable(out, func(i, j int) bool {
|
||||
return out[i].Info.IsDir()
|
||||
})
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
|
13
server/filesystem_darwin.go
Normal file
13
server/filesystem_darwin.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Returns the time that the file/folder was created.
|
||||
func (s *Stat) CTime() time.Time {
|
||||
st := s.Info.Sys().(*syscall.Stat_t)
|
||||
|
||||
return time.Unix(int64(st.Ctimespec.Sec), int64(st.Ctimespec.Nsec))
|
||||
}
|
13
server/filesystem_linux.go
Normal file
13
server/filesystem_linux.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Returns the time that the file/folder was created.
|
||||
func (s *Stat) CTime() time.Time {
|
||||
st := s.Info.Sys().(*syscall.Stat_t)
|
||||
|
||||
return time.Unix(int64(st.Ctim.Sec), int64(st.Ctim.Nsec))
|
||||
}
|
12
server/filesystem_windows.go
Normal file
12
server/filesystem_windows.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// On linux systems this will return the time that the file was created.
|
||||
// However, I have no idea how to do this on windows, so we're skipping it
|
||||
// for right now.
|
||||
func (s *Stat) CTime() time.Time {
|
||||
return s.Info.ModTime()
|
||||
}
|
Loading…
Reference in New Issue
Block a user