169 lines
4.7 KiB
Go
169 lines
4.7 KiB
Go
package fs
|
|
|
|
import (
|
|
"context"
|
|
"io/fs"
|
|
"mime"
|
|
"path"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"tildegit.org/tjp/gus"
|
|
"tildegit.org/tjp/gus/gopher"
|
|
)
|
|
|
|
// GopherFileHandler builds a handler which serves up files from a file system.
|
|
//
|
|
// It only serves responses for paths which correspond to files, not directories.
|
|
func GopherFileHandler(fileSystem fs.FS) gus.Handler {
|
|
return func(ctx context.Context, request *gus.Request) *gus.Response {
|
|
filepath, file, err := ResolveFile(request, fileSystem)
|
|
if err != nil {
|
|
return gopher.Error(err).Response()
|
|
}
|
|
|
|
if file == nil {
|
|
return nil
|
|
}
|
|
|
|
return gopher.File(GuessGopherItemType(filepath), file)
|
|
}
|
|
}
|
|
|
|
// GopherDirectoryDefault serves up default files for directory path requests.
|
|
//
|
|
// If any of the supported filenames are found in the requested directory, the
|
|
// contents of that file is returned as the gopher response.
|
|
//
|
|
// It returns nil for any paths which don't correspond to a directory.
|
|
//
|
|
// It requires that files from the provided fs.FS implement fs.ReadDirFile. If
|
|
// they don't, it will produce nil responses for all directory paths.
|
|
func GopherDirectoryDefault(fileSystem fs.FS, filenames ...string) gus.Handler {
|
|
return func(ctx context.Context, request *gus.Request) *gus.Response {
|
|
dirpath, dir, err := ResolveDirectory(request, fileSystem)
|
|
if err != nil {
|
|
return gopher.Error(err).Response()
|
|
}
|
|
if dir == nil {
|
|
return nil
|
|
}
|
|
defer func() { _ = dir.Close() }()
|
|
|
|
_, file, err := ResolveDirectoryDefault(fileSystem, dirpath, dir, filenames)
|
|
if err != nil {
|
|
return gopher.Error(err).Response()
|
|
}
|
|
if file == nil {
|
|
return nil
|
|
}
|
|
|
|
return gopher.File(gopher.MenuType, file)
|
|
}
|
|
}
|
|
|
|
// GopherDirectoryListing produces a listing of the contents of any requested directories.
|
|
//
|
|
// It returns nil for any paths which don't correspond to a filesystem directory.
|
|
//
|
|
// It requires that files from the provided fs.FS implement fs.ReadDirFile. If they
|
|
// don't, it will produce nil responses for any directory paths.
|
|
//
|
|
// A template may be nil, in which case DefaultGopherDirectoryList is used instead. The
|
|
// template is then processed with RenderDirectoryListing.
|
|
func GopherDirectoryListing(fileSystem fs.FS, tpl *template.Template) gus.Handler {
|
|
return func(ctx context.Context, request *gus.Request) *gus.Response {
|
|
dirpath, dir, err := ResolveDirectory(request, fileSystem)
|
|
if err != nil {
|
|
return gopher.Error(err).Response()
|
|
}
|
|
if dir == nil {
|
|
return nil
|
|
}
|
|
defer func() { _ = dir.Close() }()
|
|
|
|
if tpl == nil {
|
|
tpl = DefaultGopherDirectoryList
|
|
}
|
|
body, err := RenderDirectoryListing(dirpath, dir, tpl, request.Server)
|
|
if err != nil {
|
|
return gopher.Error(err).Response()
|
|
}
|
|
|
|
return gopher.File(gopher.MenuType, body)
|
|
}
|
|
}
|
|
|
|
// GopherTemplateFunctions is a map for templates providing useful functions for gophermaps.
|
|
//
|
|
// - GuessItemType: return a gopher item type for a file based on its path/name.
|
|
var GopherTemplateFunctions = template.FuncMap{
|
|
"GuessItemType": func(filepath string) string {
|
|
return string([]byte{byte(GuessGopherItemType(filepath))})
|
|
},
|
|
}
|
|
|
|
// DefaultGopherDirectoryList is a template which renders a directory listing as gophermap.
|
|
var DefaultGopherDirectoryList = template.Must(
|
|
template.New("gopher_dirlist").Funcs(GopherTemplateFunctions).Parse(
|
|
strings.ReplaceAll(
|
|
`
|
|
{{ $root := .FullPath -}}
|
|
{{ if eq .FullPath "." }}{{ $root = "" }}{{ end -}}
|
|
{{ $hostname := .Hostname -}}
|
|
{{ $port := .Port -}}
|
|
i{{ .DirName }} {{ $hostname }} {{ $port }}
|
|
i {{ $hostname }} {{ $port }}
|
|
{{ range .Entries -}}
|
|
{{ if .IsDir -}}
|
|
1{{ .Name }} {{ $root }}/{{ .Name }} {{ $hostname }} {{ $port }}
|
|
{{- else -}}
|
|
{{ GuessItemType .Name }}{{ .Name }} {{ $root }}/{{ .Name }} {{ $hostname }} {{ $port }}
|
|
{{- end }}
|
|
{{ end -}}
|
|
.
|
|
`[1:],
|
|
"\n",
|
|
"\r\n",
|
|
),
|
|
),
|
|
)
|
|
|
|
// GuessGopherItemType attempts to find the best gopher item type for a file based on its name.
|
|
func GuessGopherItemType(filepath string) gus.Status {
|
|
ext := path.Ext(filepath)
|
|
switch ext {
|
|
case "txt", "gmi":
|
|
return gopher.TextFileType
|
|
case "gif", "png", "jpg", "jpeg":
|
|
return gopher.ImageFileType
|
|
case "mp4", "mov":
|
|
return gopher.MovieFileType
|
|
case "mp3", "aiff", "aif", "aac", "ogg", "flac", "alac", "wma":
|
|
return gopher.SoundFileType
|
|
case "bmp":
|
|
return gopher.BitmapType
|
|
case "doc", "docx", "odt":
|
|
return gopher.DocumentType
|
|
case "html", "htm":
|
|
return gopher.HTMLType
|
|
case "rtf":
|
|
return gopher.RtfDocumentType
|
|
case "wav":
|
|
return gopher.WavSoundFileType
|
|
case "pdf":
|
|
return gopher.PdfDocumentType
|
|
case "xml":
|
|
return gopher.XmlDocumentType
|
|
case "":
|
|
return gopher.BinaryFileType
|
|
}
|
|
|
|
mtype := mime.TypeByExtension(ext)
|
|
if strings.HasPrefix(mtype, "text/") {
|
|
return gopher.TextFileType
|
|
}
|
|
|
|
return gopher.BinaryFileType
|
|
}
|