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/gopher.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
}