mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 04:14:39 +03:00
Gallery scrubber (#5133)
This commit is contained in:
@@ -5,8 +5,8 @@ package api
|
||||
type key int
|
||||
|
||||
const (
|
||||
// galleryKey key = 0
|
||||
performerKey key = iota + 1
|
||||
galleryKey key = 0
|
||||
performerKey
|
||||
sceneKey
|
||||
studioKey
|
||||
groupKey
|
||||
|
||||
@@ -2,8 +2,10 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/stashapp/stash/internal/api/loaders"
|
||||
"github.com/stashapp/stash/internal/api/urlbuilders"
|
||||
"github.com/stashapp/stash/internal/manager/config"
|
||||
|
||||
"github.com/stashapp/stash/pkg/image"
|
||||
@@ -189,3 +191,28 @@ func (r *galleryResolver) Urls(ctx context.Context, obj *models.Gallery) ([]stri
|
||||
|
||||
return obj.URLs.List(), nil
|
||||
}
|
||||
|
||||
func (r *galleryResolver) Paths(ctx context.Context, obj *models.Gallery) (*GalleryPathsType, error) {
|
||||
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
|
||||
builder := urlbuilders.NewGalleryURLBuilder(baseURL, obj)
|
||||
previewPath := builder.GetPreviewURL()
|
||||
|
||||
return &GalleryPathsType{
|
||||
Preview: previewPath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *galleryResolver) Image(ctx context.Context, obj *models.Gallery, index int) (ret *models.Image, err error) {
|
||||
if index < 0 {
|
||||
return nil, fmt.Errorf("index must >= 0")
|
||||
}
|
||||
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.Image.FindByGalleryIDIndex(ctx, obj.ID, uint(index))
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
116
internal/api/routes_gallery.go
Normal file
116
internal/api/routes_gallery.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
)
|
||||
|
||||
type GalleryFinder interface {
|
||||
models.GalleryGetter
|
||||
FindByChecksum(ctx context.Context, checksum string) ([]*models.Gallery, error)
|
||||
}
|
||||
|
||||
type ImageByIndexer interface {
|
||||
FindByGalleryIDIndex(ctx context.Context, galleryID int, index uint) (*models.Image, error)
|
||||
}
|
||||
|
||||
type galleryRoutes struct {
|
||||
routes
|
||||
imageRoutes imageRoutes
|
||||
galleryFinder GalleryFinder
|
||||
imageFinder ImageByIndexer
|
||||
fileGetter models.FileGetter
|
||||
}
|
||||
|
||||
func (rs galleryRoutes) Routes() chi.Router {
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Route("/{galleryId}", func(r chi.Router) {
|
||||
r.Use(rs.GalleryCtx)
|
||||
|
||||
r.Get("/preview/{imageIndex}", rs.Preview)
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (rs galleryRoutes) Preview(w http.ResponseWriter, r *http.Request) {
|
||||
g := r.Context().Value(galleryKey).(*models.Gallery)
|
||||
indexQueryParam := chi.URLParam(r, "imageIndex")
|
||||
var i *models.Image
|
||||
|
||||
index, err := strconv.Atoi(indexQueryParam)
|
||||
if err != nil || index < 0 {
|
||||
http.Error(w, "bad index", 400)
|
||||
return
|
||||
}
|
||||
|
||||
_ = rs.withReadTxn(r, func(ctx context.Context) error {
|
||||
qb := rs.imageFinder
|
||||
i, _ = qb.FindByGalleryIDIndex(ctx, g.ID, uint(index))
|
||||
// TODO - handle errors?
|
||||
|
||||
// serveThumbnail needs files populated
|
||||
if err := i.LoadPrimaryFile(ctx, rs.fileGetter); err != nil {
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
logger.Errorf("error loading primary file for image %d: %v", i.ID, err)
|
||||
}
|
||||
// set image to nil so that it doesn't try to use the primary file
|
||||
i = nil
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if i == nil {
|
||||
http.Error(w, http.StatusText(404), 404)
|
||||
return
|
||||
}
|
||||
|
||||
rs.imageRoutes.serveThumbnail(w, r, i)
|
||||
}
|
||||
|
||||
func (rs galleryRoutes) GalleryCtx(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
galleryIdentifierQueryParam := chi.URLParam(r, "galleryId")
|
||||
galleryID, _ := strconv.Atoi(galleryIdentifierQueryParam)
|
||||
|
||||
var gallery *models.Gallery
|
||||
_ = rs.withReadTxn(r, func(ctx context.Context) error {
|
||||
qb := rs.galleryFinder
|
||||
if galleryID == 0 {
|
||||
galleries, _ := qb.FindByChecksum(ctx, galleryIdentifierQueryParam)
|
||||
if len(galleries) > 0 {
|
||||
gallery = galleries[0]
|
||||
}
|
||||
} else {
|
||||
gallery, _ = qb.Find(ctx, galleryID)
|
||||
}
|
||||
|
||||
if gallery != nil {
|
||||
if err := gallery.LoadPrimaryFile(ctx, rs.fileGetter); err != nil {
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
logger.Errorf("error loading primary file for gallery %d: %v", galleryID, err)
|
||||
}
|
||||
// set image to nil so that it doesn't try to use the primary file
|
||||
gallery = nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if gallery == nil {
|
||||
http.Error(w, http.StatusText(404), 404)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), galleryKey, gallery)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
@@ -46,8 +46,12 @@ func (rs imageRoutes) Routes() chi.Router {
|
||||
}
|
||||
|
||||
func (rs imageRoutes) Thumbnail(w http.ResponseWriter, r *http.Request) {
|
||||
mgr := manager.GetInstance()
|
||||
img := r.Context().Value(imageKey).(*models.Image)
|
||||
rs.serveThumbnail(w, r, img)
|
||||
}
|
||||
|
||||
func (rs imageRoutes) serveThumbnail(w http.ResponseWriter, r *http.Request, img *models.Image) {
|
||||
mgr := manager.GetInstance()
|
||||
filepath := mgr.Paths.Generated.GetThumbnailPath(img.Checksum, models.DefaultGthumbWidth)
|
||||
|
||||
// if the thumbnail doesn't exist, encode on the fly
|
||||
|
||||
@@ -207,6 +207,7 @@ func Initialize() (*Server, error) {
|
||||
|
||||
r.Mount("/performer", server.getPerformerRoutes())
|
||||
r.Mount("/scene", server.getSceneRoutes())
|
||||
r.Mount("/gallery", server.getGalleryRoutes())
|
||||
r.Mount("/image", server.getImageRoutes())
|
||||
r.Mount("/studio", server.getStudioRoutes())
|
||||
r.Mount("/group", server.getGroupRoutes())
|
||||
@@ -326,6 +327,16 @@ func (s *Server) getSceneRoutes() chi.Router {
|
||||
}.Routes()
|
||||
}
|
||||
|
||||
func (s *Server) getGalleryRoutes() chi.Router {
|
||||
repo := s.manager.Repository
|
||||
return galleryRoutes{
|
||||
routes: routes{txnManager: repo.TxnManager},
|
||||
imageFinder: repo.Image,
|
||||
galleryFinder: repo.Gallery,
|
||||
fileGetter: repo.File,
|
||||
}.Routes()
|
||||
}
|
||||
|
||||
func (s *Server) getImageRoutes() chi.Router {
|
||||
repo := s.manager.Repository
|
||||
return imageRoutes{
|
||||
|
||||
23
internal/api/urlbuilders/gallery.go
Normal file
23
internal/api/urlbuilders/gallery.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package urlbuilders
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
)
|
||||
|
||||
type GalleryURLBuilder struct {
|
||||
BaseURL string
|
||||
GalleryID string
|
||||
}
|
||||
|
||||
func NewGalleryURLBuilder(baseURL string, gallery *models.Gallery) GalleryURLBuilder {
|
||||
return GalleryURLBuilder{
|
||||
BaseURL: baseURL,
|
||||
GalleryID: strconv.Itoa(gallery.ID),
|
||||
}
|
||||
}
|
||||
|
||||
func (b GalleryURLBuilder) GetPreviewURL() string {
|
||||
return b.BaseURL + "/gallery/" + b.GalleryID + "/preview"
|
||||
}
|
||||
Reference in New Issue
Block a user