diff --git a/graphql/documents/data/studio.graphql b/graphql/documents/data/studio.graphql index 34b2400d8..73b9e0454 100644 --- a/graphql/documents/data/studio.graphql +++ b/graphql/documents/data/studio.graphql @@ -17,10 +17,15 @@ fragment StudioData on Studio { ignore_auto_tag image_path scene_count + scene_count_all: scene_count(depth: -1) image_count + image_count_all: image_count(depth: -1) gallery_count + gallery_count_all: gallery_count(depth: -1) performer_count + performer_count_all: performer_count(depth: -1) movie_count + movie_count_all: movie_count(depth: -1) stash_ids { stash_id endpoint diff --git a/graphql/documents/data/tag.graphql b/graphql/documents/data/tag.graphql index ac773cf16..d5095fb35 100644 --- a/graphql/documents/data/tag.graphql +++ b/graphql/documents/data/tag.graphql @@ -6,10 +6,15 @@ fragment TagData on Tag { ignore_auto_tag image_path scene_count + scene_count_all: scene_count(depth: -1) scene_marker_count + scene_marker_count_all: scene_marker_count(depth: -1) image_count + image_count_all: image_count(depth: -1) gallery_count + gallery_count_all: gallery_count(depth: -1) performer_count + performer_count_all: performer_count(depth: -1) parents { ...SlimTagData diff --git a/graphql/schema/types/movie.graphql b/graphql/schema/types/movie.graphql index 14910c003..7370cdc52 100644 --- a/graphql/schema/types/movie.graphql +++ b/graphql/schema/types/movie.graphql @@ -19,7 +19,7 @@ type Movie { front_image_path: String # Resolver back_image_path: String # Resolver - scene_count: Int # Resolver + scene_count: Int! # Resolver scenes: [Scene!]! } diff --git a/graphql/schema/types/performer.graphql b/graphql/schema/types/performer.graphql index 6cbe6ed32..23e78a731 100644 --- a/graphql/schema/types/performer.graphql +++ b/graphql/schema/types/performer.graphql @@ -41,10 +41,11 @@ type Performer { ignore_auto_tag: Boolean! image_path: String # Resolver - scene_count: Int # Resolver - image_count: Int # Resolver - gallery_count: Int # Resolver - performer_count: Int # Resolver + scene_count: Int! # Resolver + image_count: Int! # Resolver + gallery_count: Int! # Resolver + movie_count: Int! # Resolver + performer_count: Int! # Resolver o_counter: Int # Resolver scenes: [Scene!]! stash_ids: [StashID!]! @@ -58,7 +59,6 @@ type Performer { weight: Int created_at: Time! updated_at: Time! - movie_count: Int movies: [Movie!]! } diff --git a/graphql/schema/types/studio.graphql b/graphql/schema/types/studio.graphql index f9b72544e..1fda1df2d 100644 --- a/graphql/schema/types/studio.graphql +++ b/graphql/schema/types/studio.graphql @@ -9,10 +9,11 @@ type Studio { ignore_auto_tag: Boolean! image_path: String # Resolver - scene_count: Int # Resolver - image_count: Int # Resolver - gallery_count: Int # Resolver - performer_count: Int # Resolver + scene_count(depth: Int): Int! # Resolver + image_count(depth: Int): Int! # Resolver + gallery_count(depth: Int): Int! # Resolver + performer_count(depth: Int): Int! # Resolver + movie_count(depth: Int): Int! # Resolver stash_ids: [StashID!]! # rating expressed as 1-5 rating: Int @deprecated(reason: "Use 1-100 range with rating100") @@ -21,7 +22,6 @@ type Studio { details: String created_at: Time! updated_at: Time! - movie_count: Int movies: [Movie!]! } diff --git a/graphql/schema/types/tag.graphql b/graphql/schema/types/tag.graphql index c6554e3b4..3af621111 100644 --- a/graphql/schema/types/tag.graphql +++ b/graphql/schema/types/tag.graphql @@ -8,11 +8,11 @@ type Tag { updated_at: Time! image_path: String # Resolver - scene_count: Int # Resolver - scene_marker_count: Int # Resolver - image_count: Int # Resolver - gallery_count: Int # Resolver - performer_count: Int + scene_count(depth: Int): Int! # Resolver + scene_marker_count(depth: Int): Int! # Resolver + image_count(depth: Int): Int! # Resolver + gallery_count(depth: Int): Int! # Resolver + performer_count(depth: Int): Int! # Resolver parents: [Tag!]! children: [Tag!]! diff --git a/internal/api/resolver_model_movie.go b/internal/api/resolver_model_movie.go index a703cb300..81e367b0b 100644 --- a/internal/api/resolver_model_movie.go +++ b/internal/api/resolver_model_movie.go @@ -71,16 +71,15 @@ func (r *movieResolver) BackImagePath(ctx context.Context, obj *models.Movie) (* return &imagePath, nil } -func (r *movieResolver) SceneCount(ctx context.Context, obj *models.Movie) (ret *int, err error) { - var res int +func (r *movieResolver) SceneCount(ctx context.Context, obj *models.Movie) (ret int, err error) { if err := r.withReadTxn(ctx, func(ctx context.Context) error { - res, err = r.repository.Scene.CountByMovieID(ctx, obj.ID) + ret, err = r.repository.Scene.CountByMovieID(ctx, obj.ID) return err }); err != nil { - return nil, err + return 0, err } - return &res, err + return ret, nil } func (r *movieResolver) Scenes(ctx context.Context, obj *models.Movie) (ret []*models.Scene, err error) { diff --git a/internal/api/resolver_model_performer.go b/internal/api/resolver_model_performer.go index afdfa6f14..acb24a0ec 100644 --- a/internal/api/resolver_model_performer.go +++ b/internal/api/resolver_model_performer.go @@ -92,40 +92,59 @@ func (r *performerResolver) Tags(ctx context.Context, obj *models.Performer) (re return ret, firstError(errs) } -func (r *performerResolver) SceneCount(ctx context.Context, obj *models.Performer) (ret *int, err error) { - var res int +func (r *performerResolver) SceneCount(ctx context.Context, obj *models.Performer) (ret int, err error) { if err := r.withReadTxn(ctx, func(ctx context.Context) error { - res, err = r.repository.Scene.CountByPerformerID(ctx, obj.ID) + ret, err = r.repository.Scene.CountByPerformerID(ctx, obj.ID) return err }); err != nil { - return nil, err + return 0, err } - return &res, nil + return ret, nil } -func (r *performerResolver) ImageCount(ctx context.Context, obj *models.Performer) (ret *int, err error) { - var res int +func (r *performerResolver) ImageCount(ctx context.Context, obj *models.Performer) (ret int, err error) { if err := r.withReadTxn(ctx, func(ctx context.Context) error { - res, err = image.CountByPerformerID(ctx, r.repository.Image, obj.ID) + ret, err = image.CountByPerformerID(ctx, r.repository.Image, obj.ID) return err }); err != nil { - return nil, err + return 0, err } - return &res, nil + return ret, nil } -func (r *performerResolver) GalleryCount(ctx context.Context, obj *models.Performer) (ret *int, err error) { - var res int +func (r *performerResolver) GalleryCount(ctx context.Context, obj *models.Performer) (ret int, err error) { if err := r.withReadTxn(ctx, func(ctx context.Context) error { - res, err = gallery.CountByPerformerID(ctx, r.repository.Gallery, obj.ID) + ret, err = gallery.CountByPerformerID(ctx, r.repository.Gallery, obj.ID) return err }); err != nil { - return nil, err + return 0, err } - return &res, nil + return ret, nil +} + +func (r *performerResolver) MovieCount(ctx context.Context, obj *models.Performer) (ret int, err error) { + if err := r.withReadTxn(ctx, func(ctx context.Context) error { + ret, err = r.repository.Movie.CountByPerformerID(ctx, obj.ID) + return err + }); err != nil { + return 0, err + } + + return ret, nil +} + +func (r *performerResolver) PerformerCount(ctx context.Context, obj *models.Performer) (ret int, err error) { + if err := r.withReadTxn(ctx, func(ctx context.Context) error { + ret, err = performer.CountByAppearsWith(ctx, r.repository.Performer, obj.ID) + return err + }); err != nil { + return 0, err + } + + return ret, nil } func (r *performerResolver) OCounter(ctx context.Context, obj *models.Performer) (ret *int, err error) { @@ -197,27 +216,3 @@ func (r *performerResolver) Movies(ctx context.Context, obj *models.Performer) ( return ret, nil } - -func (r *performerResolver) MovieCount(ctx context.Context, obj *models.Performer) (ret *int, err error) { - var res int - if err := r.withReadTxn(ctx, func(ctx context.Context) error { - res, err = r.repository.Movie.CountByPerformerID(ctx, obj.ID) - return err - }); err != nil { - return nil, err - } - - return &res, nil -} - -func (r *performerResolver) PerformerCount(ctx context.Context, obj *models.Performer) (ret *int, err error) { - var res int - if err := r.withReadTxn(ctx, func(ctx context.Context) error { - res, err = performer.CountByAppearsWith(ctx, r.repository.Performer, obj.ID) - return err - }); err != nil { - return nil, err - } - - return &res, nil -} diff --git a/internal/api/resolver_model_studio.go b/internal/api/resolver_model_studio.go index 21b2e4032..84f17eb84 100644 --- a/internal/api/resolver_model_studio.go +++ b/internal/api/resolver_model_studio.go @@ -8,7 +8,9 @@ import ( "github.com/stashapp/stash/pkg/gallery" "github.com/stashapp/stash/pkg/image" "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/movie" "github.com/stashapp/stash/pkg/performer" + "github.com/stashapp/stash/pkg/scene" ) func (r *studioResolver) ImagePath(ctx context.Context, obj *models.Studio) (*string, error) { @@ -37,52 +39,59 @@ func (r *studioResolver) Aliases(ctx context.Context, obj *models.Studio) (ret [ return ret, err } -func (r *studioResolver) SceneCount(ctx context.Context, obj *models.Studio) (ret *int, err error) { - var res int +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 { - res, err = r.repository.Scene.CountByStudioID(ctx, obj.ID) + ret, err = scene.CountByStudioID(ctx, r.repository.Scene, obj.ID, depth) return err }); err != nil { - return nil, err + return 0, err } - return &res, err + return ret, nil } -func (r *studioResolver) ImageCount(ctx context.Context, obj *models.Studio) (ret *int, err error) { - var res int +func (r *studioResolver) ImageCount(ctx context.Context, obj *models.Studio, depth *int) (ret int, err error) { if err := r.withReadTxn(ctx, func(ctx context.Context) error { - res, err = image.CountByStudioID(ctx, r.repository.Image, obj.ID) + ret, err = image.CountByStudioID(ctx, r.repository.Image, obj.ID, depth) return err }); err != nil { - return nil, err + return 0, err } - return &res, nil + return ret, nil } -func (r *studioResolver) GalleryCount(ctx context.Context, obj *models.Studio) (ret *int, err error) { - var res int +func (r *studioResolver) GalleryCount(ctx context.Context, obj *models.Studio, depth *int) (ret int, err error) { if err := r.withReadTxn(ctx, func(ctx context.Context) error { - res, err = gallery.CountByStudioID(ctx, r.repository.Gallery, obj.ID) + ret, err = gallery.CountByStudioID(ctx, r.repository.Gallery, obj.ID, depth) return err }); err != nil { - return nil, err + return 0, err } - return &res, nil + return ret, nil } -func (r *studioResolver) PerformerCount(ctx context.Context, obj *models.Studio) (ret *int, err error) { - var res int +func (r *studioResolver) PerformerCount(ctx context.Context, obj *models.Studio, depth *int) (ret int, err error) { if err := r.withReadTxn(ctx, func(ctx context.Context) error { - res, err = performer.CountByStudioID(ctx, r.repository.Performer, obj.ID) + ret, err = performer.CountByStudioID(ctx, r.repository.Performer, obj.ID, depth) return err }); err != nil { - return nil, err + return 0, err } - return &res, nil + return ret, nil +} + +func (r *studioResolver) MovieCount(ctx context.Context, obj *models.Studio, depth *int) (ret int, err error) { + if err := r.withReadTxn(ctx, func(ctx context.Context) error { + ret, err = movie.CountByStudioID(ctx, r.repository.Movie, obj.ID, depth) + return err + }); err != nil { + return 0, err + } + + return ret, nil } func (r *studioResolver) ParentStudio(ctx context.Context, obj *models.Studio) (ret *models.Studio, err error) { @@ -139,15 +148,3 @@ func (r *studioResolver) Movies(ctx context.Context, obj *models.Studio) (ret [] return ret, nil } - -func (r *studioResolver) MovieCount(ctx context.Context, obj *models.Studio) (ret *int, err error) { - var res int - if err := r.withReadTxn(ctx, func(ctx context.Context) error { - res, err = r.repository.Movie.CountByStudioID(ctx, obj.ID) - return err - }); err != nil { - return nil, err - } - - return &res, nil -} diff --git a/internal/api/resolver_model_tag.go b/internal/api/resolver_model_tag.go index bc5032a5f..778dc7fa6 100644 --- a/internal/api/resolver_model_tag.go +++ b/internal/api/resolver_model_tag.go @@ -7,6 +7,8 @@ import ( "github.com/stashapp/stash/pkg/gallery" "github.com/stashapp/stash/pkg/image" "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/performer" + "github.com/stashapp/stash/pkg/scene" ) func (r *tagResolver) Parents(ctx context.Context, obj *models.Tag) (ret []*models.Tag, err error) { @@ -42,64 +44,59 @@ func (r *tagResolver) Aliases(ctx context.Context, obj *models.Tag) (ret []strin return ret, err } -func (r *tagResolver) SceneCount(ctx context.Context, obj *models.Tag) (ret *int, err error) { - var count int +func (r *tagResolver) SceneCount(ctx context.Context, obj *models.Tag, depth *int) (ret int, err error) { if err := r.withReadTxn(ctx, func(ctx context.Context) error { - count, err = r.repository.Scene.CountByTagID(ctx, obj.ID) + ret, err = scene.CountByTagID(ctx, r.repository.Scene, obj.ID, depth) return err }); err != nil { - return nil, err + return 0, err } - return &count, err + return ret, nil } -func (r *tagResolver) SceneMarkerCount(ctx context.Context, obj *models.Tag) (ret *int, err error) { - var count int +func (r *tagResolver) SceneMarkerCount(ctx context.Context, obj *models.Tag, depth *int) (ret int, err error) { if err := r.withReadTxn(ctx, func(ctx context.Context) error { - count, err = r.repository.SceneMarker.CountByTagID(ctx, obj.ID) + ret, err = scene.MarkerCountByTagID(ctx, r.repository.SceneMarker, obj.ID, depth) return err }); err != nil { - return nil, err + return 0, err } - return &count, err + return ret, nil } -func (r *tagResolver) ImageCount(ctx context.Context, obj *models.Tag) (ret *int, err error) { - var res int +func (r *tagResolver) ImageCount(ctx context.Context, obj *models.Tag, depth *int) (ret int, err error) { if err := r.withReadTxn(ctx, func(ctx context.Context) error { - res, err = image.CountByTagID(ctx, r.repository.Image, obj.ID) + ret, err = image.CountByTagID(ctx, r.repository.Image, obj.ID, depth) return err }); err != nil { - return nil, err + return 0, err } - return &res, nil + return ret, nil } -func (r *tagResolver) GalleryCount(ctx context.Context, obj *models.Tag) (ret *int, err error) { - var res int +func (r *tagResolver) GalleryCount(ctx context.Context, obj *models.Tag, depth *int) (ret int, err error) { if err := r.withReadTxn(ctx, func(ctx context.Context) error { - res, err = gallery.CountByTagID(ctx, r.repository.Gallery, obj.ID) + ret, err = gallery.CountByTagID(ctx, r.repository.Gallery, obj.ID, depth) return err }); err != nil { - return nil, err + return 0, err } - return &res, nil + return ret, nil } -func (r *tagResolver) PerformerCount(ctx context.Context, obj *models.Tag) (ret *int, err error) { - var count int +func (r *tagResolver) PerformerCount(ctx context.Context, obj *models.Tag, depth *int) (ret int, err error) { if err := r.withReadTxn(ctx, func(ctx context.Context) error { - count, err = r.repository.Performer.CountByTagID(ctx, obj.ID) + ret, err = performer.CountByTagID(ctx, r.repository.Performer, obj.ID, depth) return err }); err != nil { - return nil, err + return 0, err } - return &count, err + return ret, nil } func (r *tagResolver) ImagePath(ctx context.Context, obj *models.Tag) (*string, error) { diff --git a/pkg/gallery/query.go b/pkg/gallery/query.go index dc97cec2b..cc2a043d7 100644 --- a/pkg/gallery/query.go +++ b/pkg/gallery/query.go @@ -35,22 +35,24 @@ func CountByPerformerID(ctx context.Context, r CountQueryer, id int) (int, error return r.QueryCount(ctx, filter, nil) } -func CountByStudioID(ctx context.Context, r CountQueryer, id int) (int, error) { +func CountByStudioID(ctx context.Context, r CountQueryer, id int, depth *int) (int, error) { filter := &models.GalleryFilterType{ Studios: &models.HierarchicalMultiCriterionInput{ Value: []string{strconv.Itoa(id)}, Modifier: models.CriterionModifierIncludes, + Depth: depth, }, } return r.QueryCount(ctx, filter, nil) } -func CountByTagID(ctx context.Context, r CountQueryer, id int) (int, error) { +func CountByTagID(ctx context.Context, r CountQueryer, id int, depth *int) (int, error) { filter := &models.GalleryFilterType{ Tags: &models.HierarchicalMultiCriterionInput{ Value: []string{strconv.Itoa(id)}, Modifier: models.CriterionModifierIncludes, + Depth: depth, }, } diff --git a/pkg/image/query.go b/pkg/image/query.go index 82125d65d..85d1df05c 100644 --- a/pkg/image/query.go +++ b/pkg/image/query.go @@ -52,22 +52,24 @@ func CountByPerformerID(ctx context.Context, r CountQueryer, id int) (int, error return r.QueryCount(ctx, filter, nil) } -func CountByStudioID(ctx context.Context, r CountQueryer, id int) (int, error) { +func CountByStudioID(ctx context.Context, r CountQueryer, id int, depth *int) (int, error) { filter := &models.ImageFilterType{ Studios: &models.HierarchicalMultiCriterionInput{ Value: []string{strconv.Itoa(id)}, Modifier: models.CriterionModifierIncludes, + Depth: depth, }, } return r.QueryCount(ctx, filter, nil) } -func CountByTagID(ctx context.Context, r CountQueryer, id int) (int, error) { +func CountByTagID(ctx context.Context, r CountQueryer, id int, depth *int) (int, error) { filter := &models.ImageFilterType{ Tags: &models.HierarchicalMultiCriterionInput{ Value: []string{strconv.Itoa(id)}, Modifier: models.CriterionModifierIncludes, + Depth: depth, }, } diff --git a/pkg/models/mocks/MovieReaderWriter.go b/pkg/models/mocks/MovieReaderWriter.go index 48eb8d8a1..edf355e14 100644 --- a/pkg/models/mocks/MovieReaderWriter.go +++ b/pkg/models/mocks/MovieReaderWriter.go @@ -384,6 +384,27 @@ func (_m *MovieReaderWriter) Query(ctx context.Context, movieFilter *models.Movi return r0, r1, r2 } +// QueryCount provides a mock function with given fields: ctx, movieFilter, findFilter +func (_m *MovieReaderWriter) QueryCount(ctx context.Context, movieFilter *models.MovieFilterType, findFilter *models.FindFilterType) (int, error) { + ret := _m.Called(ctx, movieFilter, findFilter) + + var r0 int + if rf, ok := ret.Get(0).(func(context.Context, *models.MovieFilterType, *models.FindFilterType) int); ok { + r0 = rf(ctx, movieFilter, findFilter) + } else { + r0 = ret.Get(0).(int) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *models.MovieFilterType, *models.FindFilterType) error); ok { + r1 = rf(ctx, movieFilter, findFilter) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // Update provides a mock function with given fields: ctx, updatedMovie func (_m *MovieReaderWriter) Update(ctx context.Context, updatedMovie *models.Movie) error { ret := _m.Called(ctx, updatedMovie) diff --git a/pkg/models/mocks/SceneMarkerReaderWriter.go b/pkg/models/mocks/SceneMarkerReaderWriter.go index e56ebd022..535b48029 100644 --- a/pkg/models/mocks/SceneMarkerReaderWriter.go +++ b/pkg/models/mocks/SceneMarkerReaderWriter.go @@ -252,6 +252,27 @@ func (_m *SceneMarkerReaderWriter) Query(ctx context.Context, sceneMarkerFilter return r0, r1, r2 } +// QueryCount provides a mock function with given fields: ctx, sceneMarkerFilter, findFilter +func (_m *SceneMarkerReaderWriter) QueryCount(ctx context.Context, sceneMarkerFilter *models.SceneMarkerFilterType, findFilter *models.FindFilterType) (int, error) { + ret := _m.Called(ctx, sceneMarkerFilter, findFilter) + + var r0 int + if rf, ok := ret.Get(0).(func(context.Context, *models.SceneMarkerFilterType, *models.FindFilterType) int); ok { + r0 = rf(ctx, sceneMarkerFilter, findFilter) + } else { + r0 = ret.Get(0).(int) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *models.SceneMarkerFilterType, *models.FindFilterType) error); ok { + r1 = rf(ctx, sceneMarkerFilter, findFilter) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // Update provides a mock function with given fields: ctx, updatedSceneMarker func (_m *SceneMarkerReaderWriter) Update(ctx context.Context, updatedSceneMarker *models.SceneMarker) error { ret := _m.Called(ctx, updatedSceneMarker) diff --git a/pkg/models/mocks/SceneReaderWriter.go b/pkg/models/mocks/SceneReaderWriter.go index 8d031d471..ee0c12496 100644 --- a/pkg/models/mocks/SceneReaderWriter.go +++ b/pkg/models/mocks/SceneReaderWriter.go @@ -731,6 +731,27 @@ func (_m *SceneReaderWriter) Query(ctx context.Context, options models.SceneQuer return r0, r1 } +// QueryCount provides a mock function with given fields: ctx, sceneFilter, findFilter +func (_m *SceneReaderWriter) QueryCount(ctx context.Context, sceneFilter *models.SceneFilterType, findFilter *models.FindFilterType) (int, error) { + ret := _m.Called(ctx, sceneFilter, findFilter) + + var r0 int + if rf, ok := ret.Get(0).(func(context.Context, *models.SceneFilterType, *models.FindFilterType) int); ok { + r0 = rf(ctx, sceneFilter, findFilter) + } else { + r0 = ret.Get(0).(int) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *models.SceneFilterType, *models.FindFilterType) error); ok { + r1 = rf(ctx, sceneFilter, findFilter) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // ResetOCounter provides a mock function with given fields: ctx, id func (_m *SceneReaderWriter) ResetOCounter(ctx context.Context, id int) (int, error) { ret := _m.Called(ctx, id) diff --git a/pkg/models/movie.go b/pkg/models/movie.go index 8db0e77bb..d00b3f491 100644 --- a/pkg/models/movie.go +++ b/pkg/models/movie.go @@ -37,6 +37,7 @@ type MovieReader interface { All(ctx context.Context) ([]*Movie, error) Count(ctx context.Context) (int, error) Query(ctx context.Context, movieFilter *MovieFilterType, findFilter *FindFilterType) ([]*Movie, int, error) + QueryCount(ctx context.Context, movieFilter *MovieFilterType, findFilter *FindFilterType) (int, error) GetFrontImage(ctx context.Context, movieID int) ([]byte, error) HasFrontImage(ctx context.Context, movieID int) (bool, error) GetBackImage(ctx context.Context, movieID int) ([]byte, error) diff --git a/pkg/models/scene.go b/pkg/models/scene.go index 90655ff5e..73e7b6507 100644 --- a/pkg/models/scene.go +++ b/pkg/models/scene.go @@ -178,6 +178,7 @@ type SceneReader interface { Wall(ctx context.Context, q *string) ([]*Scene, error) All(ctx context.Context) ([]*Scene, error) Query(ctx context.Context, options SceneQueryOptions) (*SceneQueryResult, error) + QueryCount(ctx context.Context, sceneFilter *SceneFilterType, findFilter *FindFilterType) (int, error) GetCover(ctx context.Context, sceneID int) ([]byte, error) HasCover(ctx context.Context, sceneID int) (bool, error) } diff --git a/pkg/models/scene_marker.go b/pkg/models/scene_marker.go index 5b653686a..deaf1ac16 100644 --- a/pkg/models/scene_marker.go +++ b/pkg/models/scene_marker.go @@ -39,6 +39,7 @@ type SceneMarkerReader interface { Count(ctx context.Context) (int, error) All(ctx context.Context) ([]*SceneMarker, error) Query(ctx context.Context, sceneMarkerFilter *SceneMarkerFilterType, findFilter *FindFilterType) ([]*SceneMarker, int, error) + QueryCount(ctx context.Context, sceneMarkerFilter *SceneMarkerFilterType, findFilter *FindFilterType) (int, error) GetTagIDs(ctx context.Context, imageID int) ([]int, error) } diff --git a/pkg/movie/query.go b/pkg/movie/query.go new file mode 100644 index 000000000..3736f9437 --- /dev/null +++ b/pkg/movie/query.go @@ -0,0 +1,28 @@ +package movie + +import ( + "context" + "strconv" + + "github.com/stashapp/stash/pkg/models" +) + +type Queryer interface { + Query(ctx context.Context, movieFilter *models.MovieFilterType, findFilter *models.FindFilterType) ([]*models.Movie, int, error) +} + +type CountQueryer interface { + QueryCount(ctx context.Context, movieFilter *models.MovieFilterType, findFilter *models.FindFilterType) (int, error) +} + +func CountByStudioID(ctx context.Context, r CountQueryer, id int, depth *int) (int, error) { + filter := &models.MovieFilterType{ + Studios: &models.HierarchicalMultiCriterionInput{ + Value: []string{strconv.Itoa(id)}, + Modifier: models.CriterionModifierIncludes, + Depth: depth, + }, + } + + return r.QueryCount(ctx, filter, nil) +} diff --git a/pkg/performer/query.go b/pkg/performer/query.go index a3045ef67..b8df03a1c 100644 --- a/pkg/performer/query.go +++ b/pkg/performer/query.go @@ -15,11 +15,24 @@ type CountQueryer interface { QueryCount(ctx context.Context, galleryFilter *models.PerformerFilterType, findFilter *models.FindFilterType) (int, error) } -func CountByStudioID(ctx context.Context, r CountQueryer, id int) (int, error) { +func CountByStudioID(ctx context.Context, r CountQueryer, id int, depth *int) (int, error) { filter := &models.PerformerFilterType{ Studios: &models.HierarchicalMultiCriterionInput{ Value: []string{strconv.Itoa(id)}, Modifier: models.CriterionModifierIncludes, + Depth: depth, + }, + } + + return r.QueryCount(ctx, filter, nil) +} + +func CountByTagID(ctx context.Context, r CountQueryer, id int, depth *int) (int, error) { + filter := &models.PerformerFilterType{ + Tags: &models.HierarchicalMultiCriterionInput{ + Value: []string{strconv.Itoa(id)}, + Modifier: models.CriterionModifierIncludes, + Depth: depth, }, } diff --git a/pkg/scene/marker_query.go b/pkg/scene/marker_query.go new file mode 100644 index 000000000..e4ae5b6df --- /dev/null +++ b/pkg/scene/marker_query.go @@ -0,0 +1,28 @@ +package scene + +import ( + "context" + "strconv" + + "github.com/stashapp/stash/pkg/models" +) + +type MarkerQueryer interface { + Query(ctx context.Context, sceneMarkerFilter *models.SceneMarkerFilterType, findFilter *models.FindFilterType) ([]*models.SceneMarker, int, error) +} + +type MarkerCountQueryer interface { + QueryCount(ctx context.Context, sceneMarkerFilter *models.SceneMarkerFilterType, findFilter *models.FindFilterType) (int, error) +} + +func MarkerCountByTagID(ctx context.Context, r MarkerCountQueryer, id int, depth *int) (int, error) { + filter := &models.SceneMarkerFilterType{ + Tags: &models.HierarchicalMultiCriterionInput{ + Value: []string{strconv.Itoa(id)}, + Modifier: models.CriterionModifierIncludes, + Depth: depth, + }, + } + + return r.QueryCount(ctx, filter, nil) +} diff --git a/pkg/scene/query.go b/pkg/scene/query.go index e910f42f0..3dc7524ed 100644 --- a/pkg/scene/query.go +++ b/pkg/scene/query.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "path/filepath" + "strconv" "strings" "github.com/stashapp/stash/pkg/job" @@ -14,6 +15,10 @@ type Queryer interface { Query(ctx context.Context, options models.SceneQueryOptions) (*models.SceneQueryResult, error) } +type CountQueryer interface { + QueryCount(ctx context.Context, sceneFilter *models.SceneFilterType, findFilter *models.FindFilterType) (int, error) +} + type IDFinder interface { Find(ctx context.Context, id int) (*models.Scene, error) FindMany(ctx context.Context, ids []int) ([]*models.Scene, error) @@ -128,3 +133,27 @@ func FilterFromPaths(paths []string) *models.SceneFilterType { return ret } + +func CountByStudioID(ctx context.Context, r CountQueryer, id int, depth *int) (int, error) { + filter := &models.SceneFilterType{ + Studios: &models.HierarchicalMultiCriterionInput{ + Value: []string{strconv.Itoa(id)}, + Modifier: models.CriterionModifierIncludes, + Depth: depth, + }, + } + + return r.QueryCount(ctx, filter, nil) +} + +func CountByTagID(ctx context.Context, r CountQueryer, id int, depth *int) (int, error) { + filter := &models.SceneFilterType{ + Tags: &models.HierarchicalMultiCriterionInput{ + Value: []string{strconv.Itoa(id)}, + Modifier: models.CriterionModifierIncludes, + Depth: depth, + }, + } + + return r.QueryCount(ctx, filter, nil) +} diff --git a/pkg/sqlite/movies.go b/pkg/sqlite/movies.go index c70ae0bae..b899c2859 100644 --- a/pkg/sqlite/movies.go +++ b/pkg/sqlite/movies.go @@ -352,7 +352,7 @@ func (qb *MovieStore) makeFilter(ctx context.Context, movieFilter *models.MovieF return query } -func (qb *MovieStore) Query(ctx context.Context, movieFilter *models.MovieFilterType, findFilter *models.FindFilterType) ([]*models.Movie, int, error) { +func (qb *MovieStore) makeQuery(ctx context.Context, movieFilter *models.MovieFilterType, findFilter *models.FindFilterType) (*queryBuilder, error) { if findFilter == nil { findFilter = &models.FindFilterType{} } @@ -371,10 +371,20 @@ func (qb *MovieStore) Query(ctx context.Context, movieFilter *models.MovieFilter filter := qb.makeFilter(ctx, movieFilter) if err := query.addFilter(filter); err != nil { - return nil, 0, err + return nil, err } query.sortAndPagination = qb.getMovieSort(findFilter) + getPagination(findFilter) + + return &query, nil +} + +func (qb *MovieStore) Query(ctx context.Context, movieFilter *models.MovieFilterType, findFilter *models.FindFilterType) ([]*models.Movie, int, error) { + query, err := qb.makeQuery(ctx, movieFilter, findFilter) + if err != nil { + return nil, 0, err + } + idsResult, countResult, err := query.executeFind(ctx) if err != nil { return nil, 0, err @@ -388,6 +398,15 @@ func (qb *MovieStore) Query(ctx context.Context, movieFilter *models.MovieFilter return movies, countResult, nil } +func (qb *MovieStore) QueryCount(ctx context.Context, movieFilter *models.MovieFilterType, findFilter *models.FindFilterType) (int, error) { + query, err := qb.makeQuery(ctx, movieFilter, findFilter) + if err != nil { + return 0, err + } + + return query.executeCount(ctx) +} + func movieIsMissingCriterionHandler(qb *MovieStore, isMissing *string) criterionHandlerFunc { return func(ctx context.Context, f *filterBuilder) { if isMissing != nil && *isMissing != "" { diff --git a/pkg/sqlite/scene.go b/pkg/sqlite/scene.go index 5f79aa099..f2e7f07c2 100644 --- a/pkg/sqlite/scene.go +++ b/pkg/sqlite/scene.go @@ -988,10 +988,7 @@ func (qb *SceneStore) addVideoFilesTable(f *filterBuilder) { f.addLeftJoin(videoFileTable, "", "video_files.file_id = scenes_files.file_id") } -func (qb *SceneStore) Query(ctx context.Context, options models.SceneQueryOptions) (*models.SceneQueryResult, error) { - sceneFilter := options.SceneFilter - findFilter := options.FindFilter - +func (qb *SceneStore) makeQuery(ctx context.Context, sceneFilter *models.SceneFilterType, findFilter *models.FindFilterType) (*queryBuilder, error) { if sceneFilter == nil { sceneFilter = &models.SceneFilterType{} } @@ -1043,7 +1040,16 @@ func (qb *SceneStore) Query(ctx context.Context, options models.SceneQueryOption qb.setSceneSort(&query, findFilter) query.sortAndPagination += getPagination(findFilter) - result, err := qb.queryGroupedFields(ctx, options, query) + return &query, nil +} + +func (qb *SceneStore) Query(ctx context.Context, options models.SceneQueryOptions) (*models.SceneQueryResult, error) { + query, err := qb.makeQuery(ctx, options.SceneFilter, options.FindFilter) + if err != nil { + return nil, err + } + + result, err := qb.queryGroupedFields(ctx, options, *query) if err != nil { return nil, fmt.Errorf("error querying aggregate fields: %w", err) } @@ -1118,6 +1124,15 @@ func (qb *SceneStore) queryGroupedFields(ctx context.Context, options models.Sce return ret, nil } +func (qb *SceneStore) QueryCount(ctx context.Context, sceneFilter *models.SceneFilterType, findFilter *models.FindFilterType) (int, error) { + query, err := qb.makeQuery(ctx, sceneFilter, findFilter) + if err != nil { + return 0, err + } + + return query.executeCount(ctx) +} + func sceneFileCountCriterionHandler(qb *SceneStore, fileCount *models.IntCriterionInput) criterionHandlerFunc { h := countCriterionHandlerBuilder{ primaryTable: sceneTable, diff --git a/pkg/sqlite/scene_marker.go b/pkg/sqlite/scene_marker.go index 490df1164..5ead4867e 100644 --- a/pkg/sqlite/scene_marker.go +++ b/pkg/sqlite/scene_marker.go @@ -252,8 +252,7 @@ func (qb *SceneMarkerStore) makeFilter(ctx context.Context, sceneMarkerFilter *m return query } - -func (qb *SceneMarkerStore) Query(ctx context.Context, sceneMarkerFilter *models.SceneMarkerFilterType, findFilter *models.FindFilterType) ([]*models.SceneMarker, int, error) { +func (qb *SceneMarkerStore) makeQuery(ctx context.Context, sceneMarkerFilter *models.SceneMarkerFilterType, findFilter *models.FindFilterType) (*queryBuilder, error) { if sceneMarkerFilter == nil { sceneMarkerFilter = &models.SceneMarkerFilterType{} } @@ -272,10 +271,20 @@ func (qb *SceneMarkerStore) Query(ctx context.Context, sceneMarkerFilter *models filter := qb.makeFilter(ctx, sceneMarkerFilter) if err := query.addFilter(filter); err != nil { - return nil, 0, err + return nil, err } query.sortAndPagination = qb.getSceneMarkerSort(&query, findFilter) + getPagination(findFilter) + + return &query, nil +} + +func (qb *SceneMarkerStore) Query(ctx context.Context, sceneMarkerFilter *models.SceneMarkerFilterType, findFilter *models.FindFilterType) ([]*models.SceneMarker, int, error) { + query, err := qb.makeQuery(ctx, sceneMarkerFilter, findFilter) + if err != nil { + return nil, 0, err + } + idsResult, countResult, err := query.executeFind(ctx) if err != nil { return nil, 0, err @@ -289,6 +298,15 @@ func (qb *SceneMarkerStore) Query(ctx context.Context, sceneMarkerFilter *models return sceneMarkers, countResult, nil } +func (qb *SceneMarkerStore) QueryCount(ctx context.Context, sceneMarkerFilter *models.SceneMarkerFilterType, findFilter *models.FindFilterType) (int, error) { + query, err := qb.makeQuery(ctx, sceneMarkerFilter, findFilter) + if err != nil { + return 0, err + } + + return query.executeCount(ctx) +} + func sceneMarkerTagIDCriterionHandler(qb *SceneMarkerStore, tagID *string) criterionHandlerFunc { return func(ctx context.Context, f *filterBuilder) { if tagID != nil { diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx index f7d50da29..5c1c43873 100644 --- a/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx @@ -211,9 +211,7 @@ export const GalleryPage: React.FC = ({ gallery }) => { - {gallery.files.length > 1 && ( - - )} + ) : undefined} diff --git a/ui/v2.5/src/components/Images/ImageDetails/Image.tsx b/ui/v2.5/src/components/Images/ImageDetails/Image.tsx index c52ae22d7..ec10a5b2e 100644 --- a/ui/v2.5/src/components/Images/ImageDetails/Image.tsx +++ b/ui/v2.5/src/components/Images/ImageDetails/Image.tsx @@ -195,9 +195,7 @@ export const Image: React.FC = () => { - {image.visual_files.length > 1 && ( - - )} + diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx index a024124c8..21ea2ff3f 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx @@ -227,13 +227,14 @@ const PerformerPage: React.FC = ({ performer }) => { + <> {intl.formatMessage({ id: "scenes" })} - + } > = ({ performer }) => { + <> {intl.formatMessage({ id: "galleries" })} - + } > = ({ performer }) => { + <> {intl.formatMessage({ id: "images" })} - + } > = ({ performer }) => { + <> {intl.formatMessage({ id: "movies" })} - + } > = ({ performer }) => { + <> {intl.formatMessage({ id: "appears_with" })} - + } > = ({ - {scene.files.length > 1 && ( - - )} + diff --git a/ui/v2.5/src/components/Shared/Counter.tsx b/ui/v2.5/src/components/Shared/Counter.tsx index c0aad2faf..b7565b1fb 100644 --- a/ui/v2.5/src/components/Shared/Counter.tsx +++ b/ui/v2.5/src/components/Shared/Counter.tsx @@ -6,16 +6,23 @@ import TextUtils from "src/utils/text"; interface IProps { abbreviateCounter?: boolean; count: number; + hideZero?: boolean; + hideOne?: boolean; } export const Counter: React.FC = ({ abbreviateCounter = false, count, + hideZero = false, + hideOne = false, }) => { const intl = useIntl(); + if (hideZero && count === 0) return null; + if (hideOne && count === 1) return null; + if (abbreviateCounter) { - const formated = TextUtils.abbreviateCounter(count); + const formatted = TextUtils.abbreviateCounter(count); return ( = ({ data-value={intl.formatNumber(count)} > - {formated.unit} + {formatted.unit} ); } else { diff --git a/ui/v2.5/src/components/Studios/StudioDetails/Studio.tsx b/ui/v2.5/src/components/Studios/StudioDetails/Studio.tsx index 049bfa076..027d3adc9 100644 --- a/ui/v2.5/src/components/Studios/StudioDetails/Studio.tsx +++ b/ui/v2.5/src/components/Studios/StudioDetails/Studio.tsx @@ -67,6 +67,19 @@ const StudioPage: React.FC = ({ studio }) => { const [updateStudio] = useStudioUpdate(); const [deleteStudio] = useStudioDestroy({ id: studio.id }); + const showAllCounts = (configuration?.ui as IUIConfig) + ?.showChildStudioContent; + const sceneCount = + (showAllCounts ? studio.scene_count_all : studio.scene_count) ?? 0; + const galleryCount = + (showAllCounts ? studio.gallery_count_all : studio.gallery_count) ?? 0; + const imageCount = + (showAllCounts ? studio.image_count_all : studio.image_count) ?? 0; + const performerCount = + (showAllCounts ? studio.performer_count_all : studio.performer_count) ?? 0; + const movieCount = + (showAllCounts ? studio.movie_count_all : studio.movie_count) ?? 0; + // set up hotkeys useEffect(() => { Mousetrap.bind("e", () => toggleEditing()); @@ -253,13 +266,14 @@ const StudioPage: React.FC = ({ studio }) => { + <> {intl.formatMessage({ id: "scenes" })} - + } > = ({ studio }) => { + <> {intl.formatMessage({ id: "galleries" })} - + } > = ({ studio }) => { + <> {intl.formatMessage({ id: "images" })} - + } > = ({ studio }) => { + <> {intl.formatMessage({ id: "performers" })} - + } > = ({ studio }) => { + <> {intl.formatMessage({ id: "movies" })} - + } > = ({ studio }) => { + <> {intl.formatMessage({ id: "subsidiary_studios" })} - + } > = ({ tag }) => { const [updateTag] = useTagUpdate(); const [deleteTag] = useTagDestroy({ id: tag.id }); + const showAllCounts = (configuration?.ui as IUIConfig)?.showChildTagContent; + const sceneCount = + (showAllCounts ? tag.scene_count_all : tag.scene_count) ?? 0; + const imageCount = + (showAllCounts ? tag.image_count_all : tag.image_count) ?? 0; + const galleryCount = + (showAllCounts ? tag.gallery_count_all : tag.gallery_count) ?? 0; + const sceneMarkerCount = + (showAllCounts ? tag.scene_marker_count_all : tag.scene_marker_count) ?? 0; + const performerCount = + (showAllCounts ? tag.performer_count_all : tag.performer_count) ?? 0; + const activeTabKey = tab === "markers" || tab === "images" || @@ -325,13 +337,14 @@ const TagPage: React.FC = ({ tag }) => { + <> {intl.formatMessage({ id: "scenes" })} - + } > @@ -339,13 +352,14 @@ const TagPage: React.FC = ({ tag }) => { + <> {intl.formatMessage({ id: "images" })} - + } > @@ -353,13 +367,14 @@ const TagPage: React.FC = ({ tag }) => { + <> {intl.formatMessage({ id: "galleries" })} - + } > = ({ tag }) => { + <> {intl.formatMessage({ id: "markers" })} - + } > @@ -384,13 +400,14 @@ const TagPage: React.FC = ({ tag }) => { + <> {intl.formatMessage({ id: "performers" })} - + } >