Skip to content

Commit

Permalink
Clear image (#722)
Browse files Browse the repository at this point in the history
* Allow clearing of tag images
* Allow clearing of studio images
* Allow clearing of performer images
* Allow clearing of movie images
* Add filtering for missing images
  • Loading branch information
WithoutPants authored Aug 11, 2020
1 parent c0afd31 commit e16118f
Show file tree
Hide file tree
Showing 24 changed files with 287 additions and 42 deletions.
4 changes: 4 additions & 0 deletions graphql/schema/types/filters.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,15 @@ input SceneFilterType {
input MovieFilterType {
"""Filter to only include movies with this studio"""
studios: MultiCriterionInput
"""Filter to only include movies missing this property"""
is_missing: String
}

input StudioFilterType {
"""Filter to only include studios with this parent studio"""
parents: MultiCriterionInput
"""Filter to only include studios missing this property"""
is_missing: String
}

input GalleryFilterType {
Expand Down
17 changes: 17 additions & 0 deletions pkg/api/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"strings"

"github.com/gobuffalo/packr/v2"
"github.com/stashapp/stash/pkg/utils"
)

var performerBox *packr.Box
Expand All @@ -30,3 +31,19 @@ func getRandomPerformerImage(gender string) ([]byte, error) {
index := rand.Intn(len(imageFiles))
return box.Find(imageFiles[index])
}

func getRandomPerformerImageUsingName(name, gender string) ([]byte, error) {
var box *packr.Box
switch strings.ToUpper(gender) {
case "FEMALE":
box = performerBox
case "MALE":
box = performerBoxMale
default:
box = performerBox

}
imageFiles := box.List()
index := utils.IntFromString(name) % uint64(len(imageFiles))
return box.Find(imageFiles[index])
}
57 changes: 39 additions & 18 deletions pkg/api/resolver_mutation_movie.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,26 @@ func (r *mutationResolver) MovieCreate(ctx context.Context, input models.MovieCr
var backimageData []byte
var err error

if input.FrontImage == nil {
// HACK: if back image is being set, set the front image to the default.
// This is because we can't have a null front image with a non-null back image.
if input.FrontImage == nil && input.BackImage != nil {
input.FrontImage = &models.DefaultMovieImage
}
if input.BackImage == nil {
input.BackImage = &models.DefaultMovieImage
}

// Process the base 64 encoded image string
_, frontimageData, err = utils.ProcessBase64Image(*input.FrontImage)
if err != nil {
return nil, err
if input.FrontImage != nil {
_, frontimageData, err = utils.ProcessBase64Image(*input.FrontImage)
if err != nil {
return nil, err
}
}

// Process the base 64 encoded image string
_, backimageData, err = utils.ProcessBase64Image(*input.BackImage)
if err != nil {
return nil, err
if input.BackImage != nil {
_, backimageData, err = utils.ProcessBase64Image(*input.BackImage)
if err != nil {
return nil, err
}
}

// Populate a new movie from the input
Expand Down Expand Up @@ -114,12 +119,14 @@ func (r *mutationResolver) MovieUpdate(ctx context.Context, input models.MovieUp
}
var frontimageData []byte
var err error
frontImageIncluded := wasFieldIncluded(ctx, "front_image")
if input.FrontImage != nil {
_, frontimageData, err = utils.ProcessBase64Image(*input.FrontImage)
if err != nil {
return nil, err
}
}
backImageIncluded := wasFieldIncluded(ctx, "back_image")
var backimageData []byte
if input.BackImage != nil {
_, backimageData, err = utils.ProcessBase64Image(*input.BackImage)
Expand Down Expand Up @@ -185,25 +192,39 @@ func (r *mutationResolver) MovieUpdate(ctx context.Context, input models.MovieUp
}

// update image table
if len(frontimageData) > 0 || len(backimageData) > 0 {
if len(frontimageData) == 0 {
if frontImageIncluded || backImageIncluded {
if !frontImageIncluded {
frontimageData, err = qb.GetFrontImage(updatedMovie.ID, tx)
if err != nil {
_ = tx.Rollback()
tx.Rollback()
return nil, err
}
}
if len(backimageData) == 0 {
if !backImageIncluded {
backimageData, err = qb.GetBackImage(updatedMovie.ID, tx)
if err != nil {
_ = tx.Rollback()
tx.Rollback()
return nil, err
}
}

if err := qb.UpdateMovieImages(movie.ID, frontimageData, backimageData, tx); err != nil {
_ = tx.Rollback()
return nil, err
if len(frontimageData) == 0 && len(backimageData) == 0 {
// both images are being nulled. Destroy them.
if err := qb.DestroyMovieImages(movie.ID, tx); err != nil {
tx.Rollback()
return nil, err
}
} else {
// HACK - if front image is null and back image is not null, then set the front image
// to the default image since we can't have a null front image and a non-null back image
if frontimageData == nil && backimageData != nil {
_, frontimageData, _ = utils.ProcessBase64Image(models.DefaultMovieImage)
}

if err := qb.UpdateMovieImages(movie.ID, frontimageData, backimageData, tx); err != nil {
_ = tx.Rollback()
return nil, err
}
}
}

Expand Down
19 changes: 10 additions & 9 deletions pkg/api/resolver_mutation_performer.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,7 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.Per
var imageData []byte
var err error

if input.Image == nil {
gender := ""
if input.Gender != nil {
gender = input.Gender.String()
}
imageData, err = getRandomPerformerImage(gender)
} else {
if input.Image != nil {
_, imageData, err = utils.ProcessBase64Image(*input.Image)
}

Expand Down Expand Up @@ -127,6 +121,7 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.Per
}
var imageData []byte
var err error
imageIncluded := wasFieldIncluded(ctx, "image")
if input.Image != nil {
_, imageData, err = utils.ProcessBase64Image(*input.Image)
if err != nil {
Expand Down Expand Up @@ -196,14 +191,20 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.Per
qb := models.NewPerformerQueryBuilder()
performer, err := qb.Update(updatedPerformer, tx)
if err != nil {
_ = tx.Rollback()
tx.Rollback()
return nil, err
}

// update image table
if len(imageData) > 0 {
if err := qb.UpdatePerformerImage(performer.ID, imageData, tx); err != nil {
_ = tx.Rollback()
tx.Rollback()
return nil, err
}
} else if imageIncluded {
// must be unsetting
if err := qb.DestroyPerformerImage(performer.ID, tx); err != nil {
tx.Rollback()
return nil, err
}
}
Expand Down
9 changes: 8 additions & 1 deletion pkg/api/resolver_mutation_studio.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ func (r *mutationResolver) StudioUpdate(ctx context.Context, input models.Studio
}

var imageData []byte
imageIncluded := wasFieldIncluded(ctx, "image")
if input.Image != nil {
var err error
_, imageData, err = utils.ProcessBase64Image(*input.Image)
Expand Down Expand Up @@ -123,7 +124,13 @@ func (r *mutationResolver) StudioUpdate(ctx context.Context, input models.Studio
// update image table
if len(imageData) > 0 {
if err := qb.UpdateStudioImage(studio.ID, imageData, tx); err != nil {
_ = tx.Rollback()
tx.Rollback()
return nil, err
}
} else if imageIncluded {
// must be unsetting
if err := qb.DestroyStudioImage(studio.ID, tx); err != nil {
tx.Rollback()
return nil, err
}
}
Expand Down
9 changes: 8 additions & 1 deletion pkg/api/resolver_mutation_tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ func (r *mutationResolver) TagUpdate(ctx context.Context, input models.TagUpdate
var imageData []byte
var err error

imageIncluded := wasFieldIncluded(ctx, "image")
if input.Image != nil {
_, imageData, err = utils.ProcessBase64Image(*input.Image)

Expand Down Expand Up @@ -116,7 +117,13 @@ func (r *mutationResolver) TagUpdate(ctx context.Context, input models.TagUpdate
// update image table
if len(imageData) > 0 {
if err := qb.UpdateTagImage(tag.ID, imageData, tx); err != nil {
_ = tx.Rollback()
tx.Rollback()
return nil, err
}
} else if imageIncluded {
// must be unsetting
if err := qb.DestroyTagImage(tag.ID, tx); err != nil {
tx.Rollback()
return nil, err
}
}
Expand Down
12 changes: 12 additions & 0 deletions pkg/api/routes_movie.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,25 @@ func (rs movieRoutes) FrontImage(w http.ResponseWriter, r *http.Request) {
movie := r.Context().Value(movieKey).(*models.Movie)
qb := models.NewMovieQueryBuilder()
image, _ := qb.GetFrontImage(movie.ID, nil)

defaultParam := r.URL.Query().Get("default")
if len(image) == 0 || defaultParam == "true" {
_, image, _ = utils.ProcessBase64Image(models.DefaultMovieImage)
}

utils.ServeImage(image, w, r)
}

func (rs movieRoutes) BackImage(w http.ResponseWriter, r *http.Request) {
movie := r.Context().Value(movieKey).(*models.Movie)
qb := models.NewMovieQueryBuilder()
image, _ := qb.GetBackImage(movie.ID, nil)

defaultParam := r.URL.Query().Get("default")
if len(image) == 0 || defaultParam == "true" {
_, image, _ = utils.ProcessBase64Image(models.DefaultMovieImage)
}

utils.ServeImage(image, w, r)
}

Expand Down
6 changes: 6 additions & 0 deletions pkg/api/routes_performer.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ func (rs performerRoutes) Image(w http.ResponseWriter, r *http.Request) {
performer := r.Context().Value(performerKey).(*models.Performer)
qb := models.NewPerformerQueryBuilder()
image, _ := qb.GetPerformerImage(performer.ID, nil)

defaultParam := r.URL.Query().Get("default")
if len(image) == 0 || defaultParam == "true" {
image, _ = getRandomPerformerImageUsingName(performer.Name.String, performer.Gender.String)
}

utils.ServeImage(image, w, r)
}

Expand Down
6 changes: 6 additions & 0 deletions pkg/api/routes_studio.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ func (rs studioRoutes) Image(w http.ResponseWriter, r *http.Request) {
studio := r.Context().Value(studioKey).(*models.Studio)
qb := models.NewStudioQueryBuilder()
image, _ := qb.GetStudioImage(studio.ID, nil)

defaultParam := r.URL.Query().Get("default")
if len(image) == 0 || defaultParam == "true" {
_, image, _ = utils.ProcessBase64Image(models.DefaultStudioImage)
}

utils.ServeImage(image, w, r)
}

Expand Down
3 changes: 2 additions & 1 deletion pkg/api/routes_tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ func (rs tagRoutes) Image(w http.ResponseWriter, r *http.Request) {
image, _ := qb.GetTagImage(tag.ID, nil)

// use default image if not present
if len(image) == 0 {
defaultParam := r.URL.Query().Get("default")
if len(image) == 0 || defaultParam == "true" {
image = models.DefaultTagImage
}

Expand Down
15 changes: 15 additions & 0 deletions pkg/models/querybuilder_movies.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,21 @@ func (qb *MovieQueryBuilder) Query(movieFilter *MovieFilterType, findFilter *Fin
havingClauses = appendClause(havingClauses, havingClause)
}

if isMissingFilter := movieFilter.IsMissing; isMissingFilter != nil && *isMissingFilter != "" {
switch *isMissingFilter {
case "front_image":
body += `left join movies_images on movies_images.movie_id = movies.id
`
whereClauses = appendClause(whereClauses, "movies_images.front_image IS NULL")
case "back_image":
body += `left join movies_images on movies_images.movie_id = movies.id
`
whereClauses = appendClause(whereClauses, "movies_images.back_image IS NULL")
default:
whereClauses = appendClause(whereClauses, "movies."+*isMissingFilter+" IS NULL")
}
}

sortAndPagination := qb.getMovieSort(findFilter) + getPagination(findFilter)
idsResult, countResult := executeFindQuery("movies", body, args, sortAndPagination, whereClauses, havingClauses)

Expand Down
4 changes: 4 additions & 0 deletions pkg/models/querybuilder_performer.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ func (qb *PerformerQueryBuilder) Query(performerFilter *PerformerFilterType, fin
switch *isMissingFilter {
case "scenes":
query.addWhere("scenes_join.scene_id IS NULL")
case "image":
query.body += `left join performers_image on performers_image.performer_id = performers.id
`
query.addWhere("performers_image.performer_id IS NULL")
default:
query.addWhere("performers." + *isMissingFilter + " IS NULL")
}
Expand Down
11 changes: 11 additions & 0 deletions pkg/models/querybuilder_studio.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,17 @@ func (qb *StudioQueryBuilder) Query(studioFilter *StudioFilterType, findFilter *
havingClauses = appendClause(havingClauses, havingClause)
}

if isMissingFilter := studioFilter.IsMissing; isMissingFilter != nil && *isMissingFilter != "" {
switch *isMissingFilter {
case "image":
body += `left join studios_image on studios_image.studio_id = studios.id
`
whereClauses = appendClause(whereClauses, "studios_image.studio_id IS NULL")
default:
whereClauses = appendClause(whereClauses, "studios."+*isMissingFilter+" IS NULL")
}
}

sortAndPagination := qb.getStudioSort(findFilter) + getPagination(findFilter)
idsResult, countResult := executeFindQuery("studios", body, args, sortAndPagination, whereClauses, havingClauses)

Expand Down
7 changes: 7 additions & 0 deletions pkg/utils/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"crypto/md5"
"crypto/rand"
"fmt"
"hash/fnv"
"io"
"os"
)
Expand Down Expand Up @@ -38,3 +39,9 @@ func GenerateRandomKey(l int) string {
rand.Read(b)
return fmt.Sprintf("%x", b)
}

func IntFromString(str string) uint64 {
h := fnv.New64a()
h.Write([]byte(str))
return h.Sum64()
}
Loading

0 comments on commit e16118f

Please sign in to comment.