Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tags to studios #4858

Merged
merged 17 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions graphql/schema/types/filters.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,8 @@ input StudioFilterType {
parents: MultiCriterionInput
"Filter by StashID"
stash_id_endpoint: StashIDCriterionInput
"Filter to only include studios with these tags"
tags: HierarchicalMultiCriterionInput
"Filter to only include studios missing this property"
is_missing: String
# rating expressed as 1-100
Expand All @@ -374,6 +376,8 @@ input StudioFilterType {
image_count: IntCriterionInput
"Filter by gallery count"
gallery_count: IntCriterionInput
"Filter by tag count"
tag_count: IntCriterionInput
"Filter by url"
url: StringCriterionInput
"Filter by studio aliases"
Expand Down Expand Up @@ -498,6 +502,9 @@ input TagFilterType {
"Filter by number of performers with this tag"
performer_count: IntCriterionInput

"Filter by number of studios with this tag"
studio_count: IntCriterionInput

"Filter by number of movies with this tag"
movie_count: IntCriterionInput

Expand Down
3 changes: 3 additions & 0 deletions graphql/schema/types/studio.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ type Studio {
parent_studio: Studio
child_studios: [Studio!]!
aliases: [String!]!
tags: [Tag!]!
ignore_auto_tag: Boolean!

image_path: String # Resolver
Expand Down Expand Up @@ -35,6 +36,7 @@ input StudioCreateInput {
favorite: Boolean
details: String
aliases: [String!]
tag_ids: [ID!]
ignore_auto_tag: Boolean
}

Expand All @@ -51,6 +53,7 @@ input StudioUpdateInput {
favorite: Boolean
details: String
aliases: [String!]
tag_ids: [ID!]
ignore_auto_tag: Boolean
}

Expand Down
1 change: 1 addition & 0 deletions graphql/schema/types/tag.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type Tag {
image_count(depth: Int): Int! # Resolver
gallery_count(depth: Int): Int! # Resolver
performer_count(depth: Int): Int! # Resolver
studio_count(depth: Int): Int! # Resolver
movie_count(depth: Int): Int! # Resolver
parents: [Tag!]!
children: [Tag!]!
Expand Down
14 changes: 14 additions & 0 deletions internal/api/resolver_model_studio.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ func (r *studioResolver) Aliases(ctx context.Context, obj *models.Studio) ([]str
return obj.Aliases.List(), nil
}

func (r *studioResolver) Tags(ctx context.Context, obj *models.Studio) (ret []*models.Tag, err error) {
if !obj.TagIDs.Loaded() {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
return obj.LoadTagIDs(ctx, r.repository.Studio)
}); err != nil {
return nil, err
}
}

var errs []error
ret, errs = loaders.From(ctx).TagByID.LoadAll(obj.TagIDs.List())
return ret, firstError(errs)
}

func (r *studioResolver) SceneCount(ctx context.Context, obj *models.Studio, depth *int) (ret int, err error) {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
ret, err = scene.CountByStudioID(ctx, r.repository.Scene, obj.ID, depth)
Expand Down
12 changes: 12 additions & 0 deletions internal/api/resolver_model_tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/stashapp/stash/pkg/movie"
"github.com/stashapp/stash/pkg/performer"
"github.com/stashapp/stash/pkg/scene"
"github.com/stashapp/stash/pkg/studio"
)

func (r *tagResolver) Parents(ctx context.Context, obj *models.Tag) (ret []*models.Tag, err error) {
Expand Down Expand Up @@ -108,6 +109,17 @@ func (r *tagResolver) PerformerCount(ctx context.Context, obj *models.Tag, depth
return ret, nil
}

func (r *tagResolver) StudioCount(ctx context.Context, obj *models.Tag, depth *int) (ret int, err error) {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
ret, err = studio.CountByTagID(ctx, r.repository.Studio, obj.ID, depth)
return err
}); err != nil {
return 0, err
}

return ret, nil
}

func (r *tagResolver) MovieCount(ctx context.Context, obj *models.Tag, depth *int) (ret int, err error) {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
ret, err = movie.CountByTagID(ctx, r.repository.Movie, obj.ID, depth)
Expand Down
10 changes: 10 additions & 0 deletions internal/api/resolver_mutation_studio.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ func (r *mutationResolver) StudioCreate(ctx context.Context, input models.Studio
return nil, fmt.Errorf("converting parent id: %w", err)
}

newStudio.TagIDs, err = translator.relatedIds(input.TagIds)
if err != nil {
return nil, fmt.Errorf("converting tag ids: %w", err)
}

// Process the base 64 encoded image string
var imageData []byte
if input.Image != nil {
Expand Down Expand Up @@ -114,6 +119,11 @@ func (r *mutationResolver) StudioUpdate(ctx context.Context, input models.Studio
return nil, fmt.Errorf("converting parent id: %w", err)
}

updatedStudio.TagIDs, err = translator.updateIds(input.TagIds, "tag_ids")
if err != nil {
return nil, fmt.Errorf("converting tag ids: %w", err)
}

// Process the base 64 encoded image string
var imageData []byte
imageIncluded := translator.hasField("image")
Expand Down
13 changes: 13 additions & 0 deletions internal/manager/task_export.go
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,7 @@ func (t *ExportTask) ExportStudios(ctx context.Context, workers int) {
func (t *ExportTask) exportStudio(ctx context.Context, wg *sync.WaitGroup, jobChan <-chan *models.Studio) {
defer wg.Done()

r := t.repository
studioReader := t.repository.Studio

for s := range jobChan {
Expand All @@ -992,6 +993,18 @@ func (t *ExportTask) exportStudio(ctx context.Context, wg *sync.WaitGroup, jobCh
continue
}

tags, err := r.Tag.FindByStudioID(ctx, s.ID)
if err != nil {
logger.Errorf("[studios] <%s> error getting studio tags: %s", s.Name, err.Error())
continue
}

newStudioJSON.Tags = tag.GetNames(tags)

if t.includeDependencies {
t.tags.IDs = sliceutil.AppendUniques(t.tags.IDs, tag.GetIDs(tags))
}

fn := newStudioJSON.Filename()

if err := t.json.saveStudio(fn, newStudioJSON); err != nil {
Expand Down
3 changes: 3 additions & 0 deletions internal/manager/task_import.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,11 @@ func (t *ImportTask) ImportStudios(ctx context.Context) {
}

func (t *ImportTask) importStudio(ctx context.Context, studioJSON *jsonschema.Studio, pendingParent map[string][]*jsonschema.Studio) error {
r := t.repository

importer := &studio.Importer{
ReaderWriter: t.repository.Studio,
TagWriter: r.Tag,
Input: *studioJSON,
MissingRefBehaviour: t.MissingRefBehaviour,
}
Expand Down
1 change: 1 addition & 0 deletions pkg/models/jsonschema/studio.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Studio struct {
Details string `json:"details,omitempty"`
Aliases []string `json:"aliases,omitempty"`
StashIDs []models.StashID `json:"stash_ids,omitempty"`
Tags []string `json:"tags,omitempty"`
IgnoreAutoTag bool `json:"ignore_auto_tag,omitempty"`
}

Expand Down
65 changes: 65 additions & 0 deletions pkg/models/mocks/StudioReaderWriter.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions pkg/models/mocks/TagReaderWriter.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions pkg/models/model_studio.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type Studio struct {
IgnoreAutoTag bool `json:"ignore_auto_tag"`

Aliases RelatedStrings `json:"aliases"`
TagIDs RelatedIDs `json:"tag_ids"`
StashIDs RelatedStashIDs `json:"stash_ids"`
}

Expand All @@ -45,6 +46,7 @@ type StudioPartial struct {
IgnoreAutoTag OptionalBool

Aliases *UpdateStrings
TagIDs *UpdateIDs
StashIDs *UpdateStashIDs
}

Expand All @@ -61,6 +63,12 @@ func (s *Studio) LoadAliases(ctx context.Context, l AliasLoader) error {
})
}

func (s *Studio) LoadTagIDs(ctx context.Context, l TagIDLoader) error {
return s.TagIDs.load(func() ([]int, error) {
return l.GetTagIDs(ctx, s.ID)
})
}

func (s *Studio) LoadStashIDs(ctx context.Context, l StashIDLoader) error {
return s.StashIDs.load(func() ([]StashID, error) {
return l.GetStashIDs(ctx, s.ID)
Expand All @@ -72,6 +80,10 @@ func (s *Studio) LoadRelationships(ctx context.Context, l PerformerReader) error
return err
}

if err := s.LoadTagIDs(ctx, l); err != nil {
return err
}

if err := s.LoadStashIDs(ctx, l); err != nil {
return err
}
Expand Down
3 changes: 3 additions & 0 deletions pkg/models/repository_studio.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type StudioFinder interface {
// StudioQueryer provides methods to query studios.
type StudioQueryer interface {
Query(ctx context.Context, studioFilter *StudioFilterType, findFilter *FindFilterType) ([]*Studio, int, error)
QueryCount(ctx context.Context, studioFilter *StudioFilterType, findFilter *FindFilterType) (int, error)
}

type StudioAutoTagQueryer interface {
Expand All @@ -36,6 +37,7 @@ type StudioAutoTagQueryer interface {
// StudioCounter provides methods to count studios.
type StudioCounter interface {
Count(ctx context.Context) (int, error)
CountByTagID(ctx context.Context, tagID int) (int, error)
}

// StudioCreator provides methods to create studios.
Expand Down Expand Up @@ -74,6 +76,7 @@ type StudioReader interface {

AliasLoader
StashIDLoader
TagIDLoader

All(ctx context.Context) ([]*Studio, error)
GetImage(ctx context.Context, studioID int) ([]byte, error)
Expand Down
1 change: 1 addition & 0 deletions pkg/models/repository_tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type TagFinder interface {
FindByPerformerID(ctx context.Context, performerID int) ([]*Tag, error)
FindByMovieID(ctx context.Context, movieID int) ([]*Tag, error)
FindBySceneMarkerID(ctx context.Context, sceneMarkerID int) ([]*Tag, error)
FindByStudioID(ctx context.Context, studioID int) ([]*Tag, error)
FindByName(ctx context.Context, name string, nocase bool) (*Tag, error)
FindByNames(ctx context.Context, names []string, nocase bool) ([]*Tag, error)
}
Expand Down
6 changes: 6 additions & 0 deletions pkg/models/studio.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ type StudioFilterType struct {
IsMissing *string `json:"is_missing"`
// Filter by rating expressed as 1-100
Rating100 *IntCriterionInput `json:"rating100"`
// Filter to only include studios with these tags
Tags *HierarchicalMultiCriterionInput `json:"tags"`
// Filter by tag count
TagCount *IntCriterionInput `json:"tag_count"`
// Filter by favorite
Favorite *bool `json:"favorite"`
// Filter by scene count
Expand Down Expand Up @@ -53,6 +57,7 @@ type StudioCreateInput struct {
Favorite *bool `json:"favorite"`
Details *string `json:"details"`
Aliases []string `json:"aliases"`
TagIds []string `json:"tag_ids"`
IgnoreAutoTag *bool `json:"ignore_auto_tag"`
}

Expand All @@ -68,5 +73,6 @@ type StudioUpdateInput struct {
Favorite *bool `json:"favorite"`
Details *string `json:"details"`
Aliases []string `json:"aliases"`
TagIds []string `json:"tag_ids"`
IgnoreAutoTag *bool `json:"ignore_auto_tag"`
}
Loading
Loading