mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 04:44:37 +03:00
Move image blobs into separate tables (#618)
* Scene cover fallback to database * Fix panic if studio not found * Fix movie studio not being imported/exported
This commit is contained in:
@@ -39,12 +39,10 @@ func (r *mutationResolver) MovieCreate(ctx context.Context, input models.MovieCr
|
||||
// Populate a new movie from the input
|
||||
currentTime := time.Now()
|
||||
newMovie := models.Movie{
|
||||
BackImage: backimageData,
|
||||
FrontImage: frontimageData,
|
||||
Checksum: checksum,
|
||||
Name: sql.NullString{String: input.Name, Valid: true},
|
||||
CreatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
|
||||
UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
|
||||
Checksum: checksum,
|
||||
Name: sql.NullString{String: input.Name, Valid: true},
|
||||
CreatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
|
||||
UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
|
||||
}
|
||||
|
||||
if input.Aliases != nil {
|
||||
@@ -90,6 +88,14 @@ func (r *mutationResolver) MovieCreate(ctx context.Context, input models.MovieCr
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// update image table
|
||||
if len(frontimageData) > 0 {
|
||||
if err := qb.UpdateMovieImages(movie.ID, frontimageData, backimageData, tx); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Commit
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
@@ -106,19 +112,20 @@ func (r *mutationResolver) MovieUpdate(ctx context.Context, input models.MovieUp
|
||||
ID: movieID,
|
||||
UpdatedAt: &models.SQLiteTimestamp{Timestamp: time.Now()},
|
||||
}
|
||||
var frontimageData []byte
|
||||
var err error
|
||||
if input.FrontImage != nil {
|
||||
_, frontimageData, err := utils.ProcessBase64Image(*input.FrontImage)
|
||||
_, frontimageData, err = utils.ProcessBase64Image(*input.FrontImage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updatedMovie.FrontImage = &frontimageData
|
||||
}
|
||||
var backimageData []byte
|
||||
if input.BackImage != nil {
|
||||
_, backimageData, err := utils.ProcessBase64Image(*input.BackImage)
|
||||
_, backimageData, err = utils.ProcessBase64Image(*input.BackImage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updatedMovie.BackImage = &backimageData
|
||||
}
|
||||
|
||||
if input.Name != nil {
|
||||
@@ -177,6 +184,29 @@ func (r *mutationResolver) MovieUpdate(ctx context.Context, input models.MovieUp
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// update image table
|
||||
if len(frontimageData) > 0 || len(backimageData) > 0 {
|
||||
if len(frontimageData) == 0 {
|
||||
frontimageData, err = qb.GetFrontImage(updatedMovie.ID, tx)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(backimageData) == 0 {
|
||||
backimageData, err = qb.GetBackImage(updatedMovie.ID, tx)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := qb.UpdateMovieImages(movie.ID, frontimageData, backimageData, tx); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Commit
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -35,7 +35,6 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.Per
|
||||
// Populate a new performer from the input
|
||||
currentTime := time.Now()
|
||||
newPerformer := models.Performer{
|
||||
Image: imageData,
|
||||
Checksum: checksum,
|
||||
CreatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
|
||||
UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
|
||||
@@ -103,6 +102,14 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.Per
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// update image table
|
||||
if len(imageData) > 0 {
|
||||
if err := qb.UpdatePerformerImage(performer.ID, imageData, tx); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Commit
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
@@ -118,12 +125,13 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.Per
|
||||
ID: performerID,
|
||||
UpdatedAt: models.SQLiteTimestamp{Timestamp: time.Now()},
|
||||
}
|
||||
var imageData []byte
|
||||
var err error
|
||||
if input.Image != nil {
|
||||
_, imageData, err := utils.ProcessBase64Image(*input.Image)
|
||||
_, imageData, err = utils.ProcessBase64Image(*input.Image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updatedPerformer.Image = imageData
|
||||
}
|
||||
if input.Name != nil {
|
||||
// generate checksum from performer name rather than image
|
||||
@@ -192,6 +200,14 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.Per
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// update image table
|
||||
if len(imageData) > 0 {
|
||||
if err := qb.UpdatePerformerImage(performer.ID, imageData, tx); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Commit
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -80,13 +80,15 @@ func (r *mutationResolver) sceneUpdate(input models.SceneUpdateInput, tx *sqlx.T
|
||||
if input.Date != nil {
|
||||
updatedScene.Date = &models.SQLiteDate{String: *input.Date, Valid: true}
|
||||
}
|
||||
|
||||
if input.CoverImage != nil && *input.CoverImage != "" {
|
||||
var err error
|
||||
_, coverImageData, err = utils.ProcessBase64Image(*input.CoverImage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updatedScene.Cover = &coverImageData
|
||||
|
||||
// update the cover after updating the scene
|
||||
}
|
||||
|
||||
if input.Rating != nil {
|
||||
@@ -111,6 +113,13 @@ func (r *mutationResolver) sceneUpdate(input models.SceneUpdateInput, tx *sqlx.T
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// update cover table
|
||||
if len(coverImageData) > 0 {
|
||||
if err := qb.UpdateSceneCover(sceneID, coverImageData, tx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the existing gallery value
|
||||
gqb := models.NewGalleryQueryBuilder()
|
||||
err = gqb.ClearGalleryId(sceneID, tx)
|
||||
@@ -188,7 +197,6 @@ func (r *mutationResolver) sceneUpdate(input models.SceneUpdateInput, tx *sqlx.T
|
||||
|
||||
// only update the cover image if provided and everything else was successful
|
||||
if coverImageData != nil {
|
||||
|
||||
err = manager.SetSceneScreenshot(scene.Checksum, coverImageData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -32,7 +32,6 @@ func (r *mutationResolver) StudioCreate(ctx context.Context, input models.Studio
|
||||
// Populate a new studio from the input
|
||||
currentTime := time.Now()
|
||||
newStudio := models.Studio{
|
||||
Image: imageData,
|
||||
Checksum: checksum,
|
||||
Name: sql.NullString{String: input.Name, Valid: true},
|
||||
CreatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
|
||||
@@ -55,6 +54,14 @@ func (r *mutationResolver) StudioCreate(ctx context.Context, input models.Studio
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// update image table
|
||||
if len(imageData) > 0 {
|
||||
if err := qb.UpdateStudioImage(studio.ID, imageData, tx); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Commit
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
@@ -71,12 +78,14 @@ func (r *mutationResolver) StudioUpdate(ctx context.Context, input models.Studio
|
||||
ID: studioID,
|
||||
UpdatedAt: &models.SQLiteTimestamp{Timestamp: time.Now()},
|
||||
}
|
||||
|
||||
var imageData []byte
|
||||
if input.Image != nil {
|
||||
_, imageData, err := utils.ProcessBase64Image(*input.Image)
|
||||
var err error
|
||||
_, imageData, err = utils.ProcessBase64Image(*input.Image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updatedStudio.Image = &imageData
|
||||
}
|
||||
if input.Name != nil {
|
||||
// generate checksum from studio name rather than image
|
||||
@@ -111,6 +120,14 @@ func (r *mutationResolver) StudioUpdate(ctx context.Context, input models.Studio
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// update image table
|
||||
if len(imageData) > 0 {
|
||||
if err := qb.UpdateStudioImage(studio.ID, imageData, tx); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Commit
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
)
|
||||
|
||||
type movieRoutes struct{}
|
||||
@@ -25,12 +26,16 @@ func (rs movieRoutes) Routes() chi.Router {
|
||||
|
||||
func (rs movieRoutes) FrontImage(w http.ResponseWriter, r *http.Request) {
|
||||
movie := r.Context().Value(movieKey).(*models.Movie)
|
||||
_, _ = w.Write(movie.FrontImage)
|
||||
qb := models.NewMovieQueryBuilder()
|
||||
image, _ := qb.GetFrontImage(movie.ID, nil)
|
||||
utils.ServeImage(image, w, r)
|
||||
}
|
||||
|
||||
func (rs movieRoutes) BackImage(w http.ResponseWriter, r *http.Request) {
|
||||
movie := r.Context().Value(movieKey).(*models.Movie)
|
||||
_, _ = w.Write(movie.BackImage)
|
||||
qb := models.NewMovieQueryBuilder()
|
||||
image, _ := qb.GetBackImage(movie.ID, nil)
|
||||
utils.ServeImage(image, w, r)
|
||||
}
|
||||
|
||||
func MovieCtx(next http.Handler) http.Handler {
|
||||
|
||||
@@ -2,13 +2,12 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
)
|
||||
|
||||
type performerRoutes struct{}
|
||||
@@ -26,17 +25,9 @@ func (rs performerRoutes) Routes() chi.Router {
|
||||
|
||||
func (rs performerRoutes) Image(w http.ResponseWriter, r *http.Request) {
|
||||
performer := r.Context().Value(performerKey).(*models.Performer)
|
||||
etag := fmt.Sprintf("%x", md5.Sum(performer.Image))
|
||||
|
||||
if match := r.Header.Get("If-None-Match"); match != "" {
|
||||
if strings.Contains(match, etag) {
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Add("Etag", etag)
|
||||
_, _ = w.Write(performer.Image)
|
||||
qb := models.NewPerformerQueryBuilder()
|
||||
image, _ := qb.GetPerformerImage(performer.ID, nil)
|
||||
utils.ServeImage(image, w, r)
|
||||
}
|
||||
|
||||
func PerformerCtx(next http.Handler) http.Handler {
|
||||
|
||||
@@ -151,7 +151,16 @@ func (rs sceneRoutes) Stream(w http.ResponseWriter, r *http.Request) {
|
||||
func (rs sceneRoutes) Screenshot(w http.ResponseWriter, r *http.Request) {
|
||||
scene := r.Context().Value(sceneKey).(*models.Scene)
|
||||
filepath := manager.GetInstance().Paths.Scene.GetScreenshotPath(scene.Checksum)
|
||||
http.ServeFile(w, r, filepath)
|
||||
|
||||
// fall back to the scene image blob if the file isn't present
|
||||
screenshotExists, _ := utils.FileExists(filepath)
|
||||
if screenshotExists {
|
||||
http.ServeFile(w, r, filepath)
|
||||
} else {
|
||||
qb := models.NewSceneQueryBuilder()
|
||||
cover, _ := qb.GetSceneCover(scene.ID, nil)
|
||||
utils.ServeImage(cover, w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (rs sceneRoutes) Preview(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -4,11 +4,12 @@ import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
)
|
||||
|
||||
type studioRoutes struct{}
|
||||
@@ -26,7 +27,10 @@ func (rs studioRoutes) Routes() chi.Router {
|
||||
|
||||
func (rs studioRoutes) Image(w http.ResponseWriter, r *http.Request) {
|
||||
studio := r.Context().Value(studioKey).(*models.Studio)
|
||||
etag := fmt.Sprintf("%x", md5.Sum(studio.Image))
|
||||
qb := models.NewStudioQueryBuilder()
|
||||
image, _ := qb.GetStudioImage(studio.ID, nil)
|
||||
|
||||
etag := fmt.Sprintf("%x", md5.Sum(image))
|
||||
if match := r.Header.Get("If-None-Match"); match != "" {
|
||||
if strings.Contains(match, etag) {
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
@@ -34,14 +38,14 @@ func (rs studioRoutes) Image(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
contentType := http.DetectContentType(studio.Image)
|
||||
contentType := http.DetectContentType(image)
|
||||
if contentType == "text/xml; charset=utf-8" || contentType == "text/plain; charset=utf-8" {
|
||||
contentType = "image/svg+xml"
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.Header().Add("Etag", etag)
|
||||
_, _ = w.Write(studio.Image)
|
||||
w.Write(image)
|
||||
}
|
||||
|
||||
func StudioCtx(next http.Handler) http.Handler {
|
||||
|
||||
Reference in New Issue
Block a user