From 127a675fc7cd9cdb65e4b4caac21e0f259102ee8 Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Mon, 27 Jan 2025 21:42:41 +0100 Subject: serve files from Storage implementation --- internal/storage/files/file.go | 102 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 internal/storage/files/file.go (limited to 'internal/storage/files/file.go') diff --git a/internal/storage/files/file.go b/internal/storage/files/file.go new file mode 100644 index 0000000..a71811c --- /dev/null +++ b/internal/storage/files/file.go @@ -0,0 +1,102 @@ +package files + +import ( + "fmt" + "hash/fnv" + "io" + "mime" + "os" + "path/filepath" + "strings" + + "gitlab.com/tozd/go/errors" + "go.alanpearce.eu/website/internal/storage" +) + +type File struct { + storage.File +} + +var encodings = map[string]string{ + "br": ".br", + "gzip": ".gz", +} + +func (r *Reader) OpenFile(path string, filename string) (*File, error) { + f, err := os.Open(filename) + if err != nil { + return nil, errors.WithMessage(err, "could not open file for reading") + } + stat, err := f.Stat() + if err != nil { + return nil, errors.WithMessage(err, "could not stat file") + } + + etag, err := etag(f) + if err != nil { + return nil, errors.WithMessage(err, "could not calculate etag") + } + + file := &File{ + File: storage.File{ + Path: path, + ContentType: mime.TypeByExtension(filepath.Ext(filename)), + LastModified: stat.ModTime(), + Etag: etag, + Encodings: map[string]io.ReadSeekCloser{ + "identity": f, + }, + }, + } + + for enc, suffix := range encodings { + _, err := os.Stat(filename + suffix) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + continue + } + + return nil, errors.WithMessagef(err, "could not stat file %s", filename+suffix) + } + file.Encodings[enc], err = os.Open(filename + suffix) + if err != nil { + return nil, errors.WithMessagef(err, "could not read file %s", filename+suffix) + } + } + + return file, nil +} + +func etag(f io.Reader) (string, error) { + hash := fnv.New64a() + if _, err := io.Copy(hash, f); err != nil { + return "", errors.WithMessage(err, "could not hash file") + } + + return fmt.Sprintf(`W/"%x"`, hash.Sum(nil)), nil +} + +func (f *File) Close() error { + var errs []error + for _, enc := range f.Encodings { + if err := enc.Close(); err != nil { + errs = append(errs, err) + } + } + + return errors.Join(errs...) +} + +func pathNameToFileName(pathname string) string { + if strings.HasSuffix(pathname, "/") { + pathname = pathname + "index.html" + } + + return pathname +} + +func fileNameToPathName(filename string) string { + pathname, _ := strings.CutSuffix(filename, "index.html") + + return pathname +} -- cgit 1.4.1