Use gallery updated at for cover mod time (#5225)

This commit is contained in:
WithoutPants 2024-09-05 16:45:15 +10:00 committed by GitHub
parent 2b288fd67c
commit ca970b9706
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 56 additions and 13 deletions

View file

@ -78,7 +78,7 @@ func (rs galleryRoutes) Cover(w http.ResponseWriter, r *http.Request) {
return return
} }
rs.imageRoutes.serveThumbnail(w, r, i) rs.imageRoutes.serveThumbnail(w, r, i, &g.UpdatedAt)
} }
func (rs galleryRoutes) Preview(w http.ResponseWriter, r *http.Request) { func (rs galleryRoutes) Preview(w http.ResponseWriter, r *http.Request) {
@ -116,7 +116,7 @@ func (rs galleryRoutes) Preview(w http.ResponseWriter, r *http.Request) {
return return
} }
rs.imageRoutes.serveThumbnail(w, r, i) rs.imageRoutes.serveThumbnail(w, r, i, nil)
} }
func (rs galleryRoutes) GalleryCtx(next http.Handler) http.Handler { func (rs galleryRoutes) GalleryCtx(next http.Handler) http.Handler {

View file

@ -7,6 +7,7 @@ import (
"net/http" "net/http"
"os/exec" "os/exec"
"strconv" "strconv"
"time"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
@ -47,17 +48,21 @@ func (rs imageRoutes) Routes() chi.Router {
func (rs imageRoutes) Thumbnail(w http.ResponseWriter, r *http.Request) { func (rs imageRoutes) Thumbnail(w http.ResponseWriter, r *http.Request) {
img := r.Context().Value(imageKey).(*models.Image) img := r.Context().Value(imageKey).(*models.Image)
rs.serveThumbnail(w, r, img) rs.serveThumbnail(w, r, img, nil)
} }
func (rs imageRoutes) serveThumbnail(w http.ResponseWriter, r *http.Request, img *models.Image) { func (rs imageRoutes) serveThumbnail(w http.ResponseWriter, r *http.Request, img *models.Image, modTime *time.Time) {
mgr := manager.GetInstance() mgr := manager.GetInstance()
filepath := mgr.Paths.Generated.GetThumbnailPath(img.Checksum, models.DefaultGthumbWidth) filepath := mgr.Paths.Generated.GetThumbnailPath(img.Checksum, models.DefaultGthumbWidth)
// if the thumbnail doesn't exist, encode on the fly // if the thumbnail doesn't exist, encode on the fly
exists, _ := fsutil.FileExists(filepath) exists, _ := fsutil.FileExists(filepath)
if exists { if exists {
utils.ServeStaticFile(w, r, filepath) if modTime == nil {
utils.ServeStaticFile(w, r, filepath)
} else {
utils.ServeStaticFileModTime(w, r, filepath, *modTime)
}
} else { } else {
const useDefault = true const useDefault = true

View file

@ -2,7 +2,10 @@ package utils
import ( import (
"bytes" "bytes"
"errors"
"io/fs"
"net/http" "net/http"
"path/filepath"
"time" "time"
"github.com/stashapp/stash/pkg/hash/md5" "github.com/stashapp/stash/pkg/hash/md5"
@ -15,14 +18,18 @@ func GenerateETag(data []byte) string {
return `"` + hash + `"` return `"` + hash + `"`
} }
// Serves static content, adding Cache-Control: no-cache and a generated ETag header. func setStaticContentCacheControl(w http.ResponseWriter, r *http.Request) {
// Responds to conditional requests using the ETag.
func ServeStaticContent(w http.ResponseWriter, r *http.Request, data []byte) {
if r.URL.Query().Has("t") { if r.URL.Query().Has("t") {
w.Header().Set("Cache-Control", "private, max-age=31536000, immutable") w.Header().Set("Cache-Control", "private, max-age=31536000, immutable")
} else { } else {
w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Cache-Control", "no-cache")
} }
}
// Serves static content, adding Cache-Control: no-cache and a generated ETag header.
// Responds to conditional requests using the ETag.
func ServeStaticContent(w http.ResponseWriter, r *http.Request, data []byte) {
setStaticContentCacheControl(w, r)
w.Header().Set("ETag", GenerateETag(data)) w.Header().Set("ETag", GenerateETag(data))
http.ServeContent(w, r, "", time.Time{}, bytes.NewReader(data)) http.ServeContent(w, r, "", time.Time{}, bytes.NewReader(data))
@ -31,11 +38,42 @@ func ServeStaticContent(w http.ResponseWriter, r *http.Request, data []byte) {
// Serves static content at filepath, adding Cache-Control: no-cache. // Serves static content at filepath, adding Cache-Control: no-cache.
// Responds to conditional requests using the file modtime. // Responds to conditional requests using the file modtime.
func ServeStaticFile(w http.ResponseWriter, r *http.Request, filepath string) { func ServeStaticFile(w http.ResponseWriter, r *http.Request, filepath string) {
if r.URL.Query().Has("t") { setStaticContentCacheControl(w, r)
w.Header().Set("Cache-Control", "private, max-age=31536000, immutable")
} else {
w.Header().Set("Cache-Control", "no-cache")
}
http.ServeFile(w, r, filepath) http.ServeFile(w, r, filepath)
} }
func toHTTPError(err error) (msg string, httpStatus int) {
if errors.Is(err, fs.ErrNotExist) {
return "404 page not found", http.StatusNotFound
}
if errors.Is(err, fs.ErrPermission) {
return "403 Forbidden", http.StatusForbidden
}
return "500 Internal Server Error", http.StatusInternalServerError
}
// ServeStaticFileModTime serves a static file at the given path using the given modTime instead of the file modTime.
func ServeStaticFileModTime(w http.ResponseWriter, r *http.Request, path string, modTime time.Time) {
setStaticContentCacheControl(w, r)
dir, file := filepath.Split(path)
fs := http.Dir(dir)
f, err := fs.Open(file)
if err != nil {
msg, code := toHTTPError(err)
http.Error(w, msg, code)
return
}
defer f.Close()
d, err := f.Stat()
if err != nil {
msg, code := toHTTPError(err)
http.Error(w, msg, code)
return
}
http.ServeContent(w, r, d.Name(), modTime, f)
}