package files import ( "fmt" "hash/fnv" "io" "mime" "os" "path/filepath" "strings" "gitlab.com/tozd/go/errors" "go.alanpearce.eu/website/internal/buffer" "go.alanpearce.eu/website/internal/storage" ) type Content io.ReadSeekCloser var encodings = map[string]string{ "br": ".br", "gzip": ".gz", } func (r *Reader) OpenFile(path string, filename string) (*storage.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") } buf := new(buffer.Buffer) if _, err := f.WriteTo(buf); err != nil { return nil, errors.WithMessage(err, "could not read file") } file := &storage.File{ Path: path, ContentType: mime.TypeByExtension(filepath.Ext(filename)), LastModified: stat.ModTime(), Etag: etag, Encodings: map[string]*buffer.Buffer{ "identity": buf, }, } 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) } bytes, err := os.ReadFile(filename + suffix) if err != nil { return nil, errors.WithMessagef(err, "could not read file %s", filename+suffix) } buf := buffer.NewBuffer(bytes) file.Encodings[enc] = buf } 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 pathNameToFileName(pathname string) string { if strings.HasSuffix(pathname, "/") { pathname = pathname + "index.html" } return strings.TrimPrefix(pathname, "/") } func fileNameToPathName(filename string) string { pathname, _ := strings.CutSuffix(filename, "index.html") return pathname }