package files import ( "errors" "fmt" "hash/fnv" "io" "mime" "os" "path/filepath" "strings" "github.com/Southclaws/fault" "github.com/Southclaws/fault/fmsg" "go.alanpearce.eu/homestead/internal/buffer" "go.alanpearce.eu/homestead/internal/storage" ) var encodings = map[string]string{ "br": ".br", "gzip": ".gz", "zstd": ".zstd", } func (r *Reader) OpenFile(path string, filename string) (*storage.File, error) { f, err := os.Open(filename) if err != nil { return nil, fault.Wrap(err, fmsg.With("could not open file for reading")) } defer f.Close() stat, err := f.Stat() if err != nil { return nil, fault.Wrap(err, fmsg.With("could not stat file")) } buf := new(buffer.Buffer) if _, err := f.WriteTo(buf); err != nil { return nil, fault.Wrap(err, fmsg.With("could not read file")) } etag, err := etag(buf) if err != nil { return nil, fault.Wrap(err, fmsg.With("could not calculate etag")) } file := &storage.File{ Path: path, ContentType: mime.TypeByExtension(filepath.Ext(filename)), LastModified: stat.ModTime(), Etag: etag, Encodings: map[string]*buffer.Buffer{ "identity": buf, }, } err = file.CalculateStyleHash() if err != nil { return nil, fault.Wrap(err) } for enc, suffix := range encodings { _, err := os.Stat(filename + suffix) if err != nil { if errors.Is(err, os.ErrNotExist) { continue } return nil, fault.Wrap( err, fmsg.With(fmt.Sprintf("could not stat file %s", filename+suffix)), ) } bytes, err := os.ReadFile(filename + suffix) if err != nil { return nil, fault.Wrap( err, fmsg.With(fmt.Sprintf("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 "", fault.Wrap(err, fmsg.With("could not hash file")) } return fmt.Sprintf(`W/"%x"`, hash.Sum(nil)), nil } func pathNameToFileName(pathname string) string { if strings.HasSuffix(pathname, "/") { pathname += "index.html" } return strings.TrimPrefix(pathname, "/") } func cutSuffix(s, suffix string) string { out, _ := strings.CutSuffix(s, suffix) return out } func fileNameToPathName(filename string) string { return cutSuffix( cutSuffix(filename, "index.html"), ".html", ) }