Skip to content

Commit 4f78aa0

Browse files
authored
Merge pull request #17 from RimuruOtter/paged-dir-query
Adds Paged Directory Queries for supporting large file throughput
2 parents f131de3 + 1be2b0e commit 4f78aa0

2 files changed

Lines changed: 87 additions & 48 deletions

File tree

src/router/router_server_files.go

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,33 @@ func getServerFileContents(c *gin.Context) {
7878
func getServerListDirectory(c *gin.Context) {
7979
s := ExtractServer(c)
8080
dir := c.Query("directory")
81-
if stats, err := s.Filesystem().ListDirectory(dir); err != nil {
82-
middleware.CaptureAndAbort(c, err)
83-
} else {
81+
82+
offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))
83+
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "0"))
84+
85+
if limit > 0 {
86+
stats, total, err := s.Filesystem().ListDirectoryPaged(c.Request.Context(), dir, offset, limit)
87+
if err != nil {
88+
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
89+
c.Abort()
90+
return
91+
}
92+
middleware.CaptureAndAbort(c, err)
93+
return
94+
}
95+
96+
c.Header("X-Total-Count", strconv.Itoa(total))
8497
c.JSON(http.StatusOK, stats)
98+
return
99+
}
100+
101+
stats, err := s.Filesystem().ListDirectory(dir)
102+
if err != nil {
103+
middleware.CaptureAndAbort(c, err)
104+
return
85105
}
106+
c.Header("X-Total-Count", strconv.Itoa(len(stats)))
107+
c.JSON(http.StatusOK, stats)
86108
}
87109

88110
type renameFile struct {

src/server/filesystem/filesystem.go

Lines changed: 62 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package filesystem
22

33
import (
4+
"context"
45
"fmt"
56
"io"
67
"os"
@@ -13,8 +14,7 @@ import (
1314
"time"
1415

1516
"emperror.dev/errors"
16-
"github.com/apex/log"
17-
"github.com/gabriel-vasile/mimetype"
17+
// "github.com/apex/log" // Was only used for the listDirectory function for Mimetype which has now been removed for performance.
1818
ignore "github.com/sabhiram/go-gitignore"
1919

2020
"github.com/pyrohost/elytra/src/config"
@@ -429,64 +429,81 @@ func (fs *Filesystem) ListDirectory(p string) ([]Stat, error) {
429429
return Stat{}, err
430430
}
431431

432-
var d string
432+
mt := "application/octet-stream"
433433
if e.Type().IsDir() {
434-
d = "inode/directory"
435-
} else {
436-
d = "application/octet-stream"
434+
mt = "inode/directory"
437435
}
438-
var m *mimetype.MIME
439-
if e.Type().IsRegular() {
440-
// TODO: I should probably find a better way to do this.
441-
eO := e.(interface {
442-
Open() (ufs.File, error)
443-
})
444-
f, err := eO.Open()
445-
if err != nil {
446-
return Stat{}, err
447-
}
448-
m, err = mimetype.DetectReader(f)
449-
if err != nil {
450-
log.Error(err.Error())
451-
}
452-
_ = f.Close()
453-
}
454-
455-
st := Stat{FileInfo: info, Mimetype: d}
456-
if m != nil {
457-
st.Mimetype = m.String()
458-
}
459-
return st, nil
436+
return Stat{FileInfo: info, Mimetype: mt}, nil
460437
})
461438
if err != nil {
462439
return nil, err
463440
}
464441

465-
// Sort entries alphabetically.
466-
slices.SortStableFunc(out, func(a, b Stat) int {
467-
switch {
468-
case a.Name() == b.Name():
469-
return 0
470-
case a.Name() > b.Name():
442+
// Sort: dirs first, then name
443+
slices.SortFunc(out, func(a, b Stat) int {
444+
if a.IsDir() != b.IsDir() {
445+
if a.IsDir() {
446+
return -1
447+
}
471448
return 1
472-
default:
473-
return -1
474449
}
450+
return strings.Compare(a.Name(), b.Name())
475451
})
452+
return out, nil
453+
}
476454

477-
// Sort folders before other file types.
478-
slices.SortStableFunc(out, func(a, b Stat) int {
479-
switch {
480-
case a.IsDir() && b.IsDir():
481-
return 0
482-
case a.IsDir():
483-
return -1
484-
default:
455+
func (fs *Filesystem) ListDirectoryPaged(ctx context.Context, p string, offset, limit int) ([]Stat, int, error) {
456+
if offset < 0 {
457+
offset = 0
458+
}
459+
460+
if limit <= 0 {
461+
return []Stat{}, 0, nil
462+
}
463+
464+
entries, err := fs.unixFS.ReadDir(p)
465+
if err != nil {
466+
return nil, 0, err
467+
}
468+
total := len(entries)
469+
if offset >= total {
470+
return []Stat{}, total, nil
471+
}
472+
473+
// Sort dirs first, then name
474+
slices.SortFunc(entries, func(a, b ufs.DirEntry) int {
475+
if a.Type().IsDir() != b.Type().IsDir() {
476+
if a.Type().IsDir() {
477+
return -1
478+
}
485479
return 1
486480
}
481+
return strings.Compare(a.Name(), b.Name())
487482
})
488483

489-
return out, nil
484+
end := offset + limit
485+
if end > total {
486+
end = total
487+
}
488+
489+
out := make([]Stat, 0, end-offset)
490+
for _, e := range entries[offset:end] {
491+
select {
492+
case <-ctx.Done():
493+
return nil, 0, ctx.Err()
494+
default:
495+
}
496+
info, err := e.Info()
497+
if err != nil {
498+
return nil, 0, err
499+
}
500+
mt := "application/octet-stream"
501+
if e.Type().IsDir() {
502+
mt = "inode/directory"
503+
}
504+
out = append(out, Stat{FileInfo: info, Mimetype: mt})
505+
}
506+
return out, total, nil
490507
}
491508

492509
func (fs *Filesystem) Chtimes(path string, atime, mtime time.Time) error {

0 commit comments

Comments
 (0)