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/docker v0.0.0-20180422163414-57142e89befe
|
||||||
github.com/docker/go-connections v0.4.0
|
github.com/docker/go-connections v0.4.0
|
||||||
github.com/docker/go-units v0.3.3 // indirect
|
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/gogo/protobuf v1.0.0 // indirect
|
||||||
github.com/google/go-cmp v0.2.0 // indirect
|
github.com/google/go-cmp v0.2.0 // indirect
|
||||||
github.com/gotestyourself/gotestyourself v2.2.0+incompatible // 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)
|
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.
|
// Configures the router and all of the associated routes.
|
||||||
func (rt *Router) ConfigureRouter() *httprouter.Router {
|
func (rt *Router) ConfigureRouter() *httprouter.Router {
|
||||||
router := httprouter.New()
|
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", rt.AuthenticateToken("i:servers", rt.routeAllServers))
|
||||||
router.GET("/api/servers/:server", rt.AuthenticateToken("s:view", rt.AuthenticateServer(rt.routeServer)))
|
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/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)))
|
router.POST("/api/servers/:server/power", rt.AuthenticateToken("s:power", rt.AuthenticateServer(rt.routeServerPower)))
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/gabriel-vasile/mimetype"
|
"github.com/gabriel-vasile/mimetype"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
@ -9,6 +10,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -122,7 +124,7 @@ func (fs *Filesystem) HasSpaceAvailable() bool {
|
||||||
if size, err := fs.DirectorySize("/"); err != nil {
|
if size, err := fs.DirectorySize("/"); err != nil {
|
||||||
zap.S().Warnw("failed to determine directory size", zap.String("server", fs.Server.Uuid), zap.Error(err))
|
zap.S().Warnw("failed to determine directory size", zap.String("server", fs.Server.Uuid), zap.Error(err))
|
||||||
} else {
|
} else {
|
||||||
fs.Server.Cache().Set("disk_used", size, time.Minute * 5)
|
fs.Server.Cache().Set("disk_used", size, time.Minute*5)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,10 +204,34 @@ func (fs *Filesystem) DeleteFile(p string) error {
|
||||||
|
|
||||||
// Defines the stat struct object.
|
// Defines the stat struct object.
|
||||||
type Stat struct {
|
type Stat struct {
|
||||||
Info *os.FileInfo
|
Info os.FileInfo
|
||||||
Mimetype string
|
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
|
// 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.
|
// MIME data that can be used for editing files.
|
||||||
func (fs *Filesystem) Stat(p string) (*Stat, error) {
|
func (fs *Filesystem) Stat(p string) (*Stat, error) {
|
||||||
|
@ -228,9 +254,66 @@ func (fs *Filesystem) Stat(p string) (*Stat, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
st := &Stat{
|
st := &Stat{
|
||||||
Info: &s,
|
Info: s,
|
||||||
Mimetype: m,
|
Mimetype: m,
|
||||||
}
|
}
|
||||||
|
|
||||||
return st, nil
|
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