155 lines
3.3 KiB
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
|
|
}
|