This repository has been archived on 2023-05-01. You can view files and clone it, but cannot push or open issues or pull requests.
gus/contrib/fs/dir.go

155 lines
3.3 KiB
Go

package fs
import (
"bytes"
"io"
"io/fs"
"sort"
"strings"
"text/template"
"tildegit.org/tjp/gus"
)
// ResolveDirectory opens the directory corresponding to a request path.
//
// The string is the full path to the directory. If the returned ReadDirFile
// is not nil, it will be open and must be closed by the caller.
func ResolveDirectory(
request *gus.Request,
fileSystem fs.FS,
) (string, fs.ReadDirFile, error) {
path := strings.Trim(request.Path, "/")
if path == "" {
path = "."
}
file, err := fileSystem.Open(path)
if isNotFound(err) {
return "", nil, nil
}
if err != nil {
return "", nil, err
}
isDir, err := fileIsDir(file)
if err != nil {
_ = file.Close()
return "", nil, err
}
if !isDir {
_ = file.Close()
return "", nil, nil
}
dirFile, ok := file.(fs.ReadDirFile)
if !ok {
_ = file.Close()
return "", nil, nil
}
return path, dirFile, nil
}
// ResolveDirectoryDefault finds any of the provided filenames within a directory.
//
// If it does not find any of the filenames it returns "", nil, nil.
func ResolveDirectoryDefault(
fileSystem fs.FS,
dirPath string,
dir fs.ReadDirFile,
filenames []string,
) (string, fs.File, error) {
entries, err := dir.ReadDir(0)
if err != nil {
return "", nil, err
}
sort.Slice(entries, func(a, b int) bool {
return entries[a].Name() < entries[b].Name()
})
for _, filename := range filenames {
idx := sort.Search(len(entries), func(i int) bool {
return entries[i].Name() <= filename
})
if idx < len(entries) && entries[idx].Name() == filename {
path := dirPath + "/" + filename
file, err := fileSystem.Open(path)
return path, file, err
}
}
return "", nil, nil
}
// RenderDirectoryListing provides an io.Reader with the output of a directory listing template.
//
// The template is provided the following namespace:
// - .FullPath: the complete path to the listed directory
// - .DirName: the name of the directory itself
// - .Entries: the []fs.DirEntry of the directory contents
// - .Hostname: the hostname of the server hosting the file
// - .Port: the port on which the server is listening
//
// Each entry in .Entries has the following fields:
// - .Name the string name of the item within the directory
// - .IsDir is a boolean
// - .Type is the FileMode bits
// - .Info is a method returning (fs.FileInfo, error)
func RenderDirectoryListing(
path string,
dir fs.ReadDirFile,
template *template.Template,
server gus.Server,
) (io.Reader, error) {
buf := &bytes.Buffer{}
environ, err := dirlistNamespace(path, dir, server)
if err != nil {
return nil, err
}
if err := template.Execute(buf, environ); err != nil {
return nil, err
}
return buf, nil
}
func dirlistNamespace(path string, dirFile fs.ReadDirFile, server gus.Server) (map[string]any, error) {
entries, err := dirFile.ReadDir(0)
if err != nil {
return nil, err
}
sort.Slice(entries, func(i, j int) bool {
return entries[i].Name() < entries[j].Name()
})
var dirname string
if path == "." {
dirname = "(root)"
} else {
dirname = path[strings.LastIndex(path, "/")+1:]
}
hostname := "none"
port := "0"
if server != nil {
hostname = server.Hostname()
port = server.Port()
}
m := map[string]any{
"FullPath": path,
"DirName": dirname,
"Entries": entries,
"Hostname": hostname,
"Port": port,
}
return m, nil
}