diff --git a/graphql/schema/types/filters.graphql b/graphql/schema/types/filters.graphql index 643029db2..3164a010b 100644 --- a/graphql/schema/types/filters.graphql +++ b/graphql/schema/types/filters.graphql @@ -143,6 +143,8 @@ input PerformerFilterType { image_count: IntCriterionInput "Filter by gallery count" gallery_count: IntCriterionInput + "Filter by play count" + play_count: IntCriterionInput "Filter by o count" o_counter: IntCriterionInput "Filter by StashID" diff --git a/pkg/models/performer.go b/pkg/models/performer.go index 9449d611d..9f5b1b51f 100644 --- a/pkg/models/performer.go +++ b/pkg/models/performer.go @@ -160,6 +160,8 @@ type PerformerFilterType struct { ImageCount *IntCriterionInput `json:"image_count"` // Filter by gallery count GalleryCount *IntCriterionInput `json:"gallery_count"` + // Filter by play count + PlayCount *IntCriterionInput `json:"play_count"` // Filter by O count OCounter *IntCriterionInput `json:"o_counter"` // Filter by StashID diff --git a/pkg/sqlite/performer.go b/pkg/sqlite/performer.go index 794c2b6cc..f10706b45 100644 --- a/pkg/sqlite/performer.go +++ b/pkg/sqlite/performer.go @@ -670,6 +670,7 @@ func (qb *PerformerStore) makeFilter(ctx context.Context, filter *models.Perform query.handleCriterion(ctx, performerSceneCountCriterionHandler(qb, filter.SceneCount)) query.handleCriterion(ctx, performerImageCountCriterionHandler(qb, filter.ImageCount)) query.handleCriterion(ctx, performerGalleryCountCriterionHandler(qb, filter.GalleryCount)) + query.handleCriterion(ctx, performerPlayCounterCriterionHandler(qb, filter.PlayCount)) query.handleCriterion(ctx, performerOCounterCriterionHandler(qb, filter.OCounter)) query.handleCriterion(ctx, dateCriterionHandler(filter.Birthdate, tableName+".birthdate")) query.handleCriterion(ctx, dateCriterionHandler(filter.DeathDate, tableName+".death_date")) @@ -874,6 +875,63 @@ var selectPerformerOCountSQL = utils.StrFormat( }, ) +// used for sorting and filtering play count on performer view count +var selectPerformerPlayCountSQL = utils.StrFormat( + "SELECT COUNT(DISTINCT {view_date}) FROM ("+ + "SELECT {view_date} FROM {performers_scenes} s "+ + "LEFT JOIN {scenes} ON {scenes}.id = s.{scene_id} "+ + "LEFT JOIN {scenes_view_dates} ON {scenes_view_dates}.{scene_id} = {scenes}.id "+ + "WHERE s.{performer_id} = {performers}.id"+ + ")", + map[string]interface{}{ + "performer_id": performerIDColumn, + "performers": performerTable, + "performers_scenes": performersScenesTable, + "scenes": sceneTable, + "scene_id": sceneIDColumn, + "scenes_view_dates": scenesViewDatesTable, + "view_date": sceneViewDateColumn, + }, +) + +// used for sorting on performer last o_date +var selectPerformerLastOAtSQL = utils.StrFormat( + "SELECT MAX(o_date) FROM ("+ + "SELECT {o_date} FROM {performers_scenes} s "+ + "LEFT JOIN {scenes} ON {scenes}.id = s.{scene_id} "+ + "LEFT JOIN {scenes_o_dates} ON {scenes_o_dates}.{scene_id} = {scenes}.id "+ + "WHERE s.{performer_id} = {performers}.id"+ + ")", + map[string]interface{}{ + "performer_id": performerIDColumn, + "performers": performerTable, + "performers_scenes": performersScenesTable, + "scenes": sceneTable, + "scene_id": sceneIDColumn, + "scenes_o_dates": scenesODatesTable, + "o_date": sceneODateColumn, + }, +) + +// used for sorting on performer last view_date +var selectPerformerLastPlayedAtSQL = utils.StrFormat( + "SELECT MAX(view_date) FROM ("+ + "SELECT {view_date} FROM {performers_scenes} s "+ + "LEFT JOIN {scenes} ON {scenes}.id = s.{scene_id} "+ + "LEFT JOIN {scenes_view_dates} ON {scenes_view_dates}.{scene_id} = {scenes}.id "+ + "WHERE s.{performer_id} = {performers}.id"+ + ")", + map[string]interface{}{ + "performer_id": performerIDColumn, + "performers": performerTable, + "performers_scenes": performersScenesTable, + "scenes": sceneTable, + "scene_id": sceneIDColumn, + "scenes_view_dates": scenesViewDatesTable, + "view_date": sceneViewDateColumn, + }, +) + func performerOCounterCriterionHandler(qb *PerformerStore, count *models.IntCriterionInput) criterionHandlerFunc { return func(ctx context.Context, f *filterBuilder) { if count == nil { @@ -887,6 +945,19 @@ func performerOCounterCriterionHandler(qb *PerformerStore, count *models.IntCrit } } +func performerPlayCounterCriterionHandler(qb *PerformerStore, count *models.IntCriterionInput) criterionHandlerFunc { + return func(ctx context.Context, f *filterBuilder) { + if count == nil { + return + } + + lhs := "(" + selectPerformerPlayCountSQL + ")" + clause, args := getIntCriterionWhereClause(lhs, *count) + + f.addWhere(clause, args...) + } +} + func performerStudiosCriterionHandler(qb *PerformerStore, studios *models.HierarchicalMultiCriterionInput) criterionHandlerFunc { return func(ctx context.Context, f *filterBuilder) { if studios != nil { @@ -1027,6 +1098,21 @@ func (qb *PerformerStore) sortByOCounter(direction string) string { return " ORDER BY (" + selectPerformerOCountSQL + ") " + direction } +func (qb *PerformerStore) sortByPlayCount(direction string) string { + // need to sum the o_counter from scenes and images + return " ORDER BY (" + selectPerformerPlayCountSQL + ") " + direction +} + +func (qb *PerformerStore) sortByLastOAt(direction string) string { + // need to get the o_dates from scenes + return " ORDER BY (" + selectPerformerLastOAtSQL + ") " + direction +} + +func (qb *PerformerStore) sortByLastPlayedAt(direction string) string { + // need to get the view_dates from scenes + return " ORDER BY (" + selectPerformerLastPlayedAtSQL + ") " + direction +} + func (qb *PerformerStore) getPerformerSort(findFilter *models.FindFilterType) string { var sort string var direction string @@ -1048,8 +1134,14 @@ func (qb *PerformerStore) getPerformerSort(findFilter *models.FindFilterType) st sortQuery += getCountSort(performerTable, performersImagesTable, performerIDColumn, direction) case "galleries_count": sortQuery += getCountSort(performerTable, performersGalleriesTable, performerIDColumn, direction) + case "play_count": + sortQuery += qb.sortByPlayCount(direction) case "o_counter": sortQuery += qb.sortByOCounter(direction) + case "last_played_at": + sortQuery += qb.sortByLastPlayedAt(direction) + case "last_o_at": + sortQuery += qb.sortByLastOAt(direction) default: sortQuery += getSort(sort, direction, "performers") } diff --git a/ui/v2.5/src/components/Performers/PerformerListTable.tsx b/ui/v2.5/src/components/Performers/PerformerListTable.tsx index 183cfbf3a..756efa979 100644 --- a/ui/v2.5/src/components/Performers/PerformerListTable.tsx +++ b/ui/v2.5/src/components/Performers/PerformerListTable.tsx @@ -350,7 +350,7 @@ export const PerformerListTable: React.FC = ( }, { value: "o_counter", - label: intl.formatMessage({ id: "o_counter" }), + label: intl.formatMessage({ id: "o_count" }), defaultShow: true, render: OCounterCell, }, diff --git a/ui/v2.5/src/components/Scenes/SceneListTable.tsx b/ui/v2.5/src/components/Scenes/SceneListTable.tsx index 2e2c969fb..5500d096e 100644 --- a/ui/v2.5/src/components/Scenes/SceneListTable.tsx +++ b/ui/v2.5/src/components/Scenes/SceneListTable.tsx @@ -334,7 +334,7 @@ export const SceneListTable: React.FC = ( }, { value: "o_counter", - label: intl.formatMessage({ id: "o_counter" }), + label: intl.formatMessage({ id: "o_count" }), render: (s) => <>{s.o_counter}, }, { diff --git a/ui/v2.5/src/components/Scenes/SceneMergeDialog.tsx b/ui/v2.5/src/components/Scenes/SceneMergeDialog.tsx index 676c2303b..55301f926 100644 --- a/ui/v2.5/src/components/Scenes/SceneMergeDialog.tsx +++ b/ui/v2.5/src/components/Scenes/SceneMergeDialog.tsx @@ -410,7 +410,7 @@ const SceneMergeDetails: React.FC = ({ onChange={(value) => setRating(value)} /> (