Added Sort Performers by Last O At / Last Played At / Play Count and Added Filter Performers by Play Count. Changes to display O Count rather than O-Counter for better consistency. Grammar fixes for 'Interactive Speed' and 'pHash'. (#4649)

* Sort Performers by Last O / View

Added 2 New Sorts 'Last O At' and 'Last Played At' for Performers

* Filter Performers by Play Count

Was not sure whether to label this 'views' as the code does, or 'plays' but chose the latter as it gives parity across the scenes and performers filters.

* Sort Performers by Play Count

Reutilised the prior selectPerformerLastOAtSQL code that was used to filter by play count to additionally provide useful sorting options.

* Replaced O-Counter with O Count

To better match other sort and filter options like Gallery Count, Image Count, Play Count, Scene Count, Tag Count, File Count, Performer Count and Play Count, we should really use O Count rather than O-Counter for increased legibility and coherence.

* Title Case on 'Interactive speed' and correct capitalization for 'phash'

Every other filter/sort option is using Title Case other than 'Interactive speed' which stands out as incorrect. Also, fixing the correct mid-word capitalization on phash to pHash.

* Formatting

Formatted source code and Ran all tests
This commit is contained in:
randemgame
2024-03-14 01:32:08 +02:00
committed by GitHub
parent ae6d1a8109
commit 3d0a8f653a
10 changed files with 128 additions and 22 deletions

View File

@@ -143,6 +143,8 @@ input PerformerFilterType {
image_count: IntCriterionInput image_count: IntCriterionInput
"Filter by gallery count" "Filter by gallery count"
gallery_count: IntCriterionInput gallery_count: IntCriterionInput
"Filter by play count"
play_count: IntCriterionInput
"Filter by o count" "Filter by o count"
o_counter: IntCriterionInput o_counter: IntCriterionInput
"Filter by StashID" "Filter by StashID"

View File

@@ -160,6 +160,8 @@ type PerformerFilterType struct {
ImageCount *IntCriterionInput `json:"image_count"` ImageCount *IntCriterionInput `json:"image_count"`
// Filter by gallery count // Filter by gallery count
GalleryCount *IntCriterionInput `json:"gallery_count"` GalleryCount *IntCriterionInput `json:"gallery_count"`
// Filter by play count
PlayCount *IntCriterionInput `json:"play_count"`
// Filter by O count // Filter by O count
OCounter *IntCriterionInput `json:"o_counter"` OCounter *IntCriterionInput `json:"o_counter"`
// Filter by StashID // Filter by StashID

View File

@@ -670,6 +670,7 @@ func (qb *PerformerStore) makeFilter(ctx context.Context, filter *models.Perform
query.handleCriterion(ctx, performerSceneCountCriterionHandler(qb, filter.SceneCount)) query.handleCriterion(ctx, performerSceneCountCriterionHandler(qb, filter.SceneCount))
query.handleCriterion(ctx, performerImageCountCriterionHandler(qb, filter.ImageCount)) query.handleCriterion(ctx, performerImageCountCriterionHandler(qb, filter.ImageCount))
query.handleCriterion(ctx, performerGalleryCountCriterionHandler(qb, filter.GalleryCount)) query.handleCriterion(ctx, performerGalleryCountCriterionHandler(qb, filter.GalleryCount))
query.handleCriterion(ctx, performerPlayCounterCriterionHandler(qb, filter.PlayCount))
query.handleCriterion(ctx, performerOCounterCriterionHandler(qb, filter.OCounter)) query.handleCriterion(ctx, performerOCounterCriterionHandler(qb, filter.OCounter))
query.handleCriterion(ctx, dateCriterionHandler(filter.Birthdate, tableName+".birthdate")) query.handleCriterion(ctx, dateCriterionHandler(filter.Birthdate, tableName+".birthdate"))
query.handleCriterion(ctx, dateCriterionHandler(filter.DeathDate, tableName+".death_date")) 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 { func performerOCounterCriterionHandler(qb *PerformerStore, count *models.IntCriterionInput) criterionHandlerFunc {
return func(ctx context.Context, f *filterBuilder) { return func(ctx context.Context, f *filterBuilder) {
if count == nil { 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 { func performerStudiosCriterionHandler(qb *PerformerStore, studios *models.HierarchicalMultiCriterionInput) criterionHandlerFunc {
return func(ctx context.Context, f *filterBuilder) { return func(ctx context.Context, f *filterBuilder) {
if studios != nil { if studios != nil {
@@ -1027,6 +1098,21 @@ func (qb *PerformerStore) sortByOCounter(direction string) string {
return " ORDER BY (" + selectPerformerOCountSQL + ") " + direction 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 { func (qb *PerformerStore) getPerformerSort(findFilter *models.FindFilterType) string {
var sort string var sort string
var direction string var direction string
@@ -1048,8 +1134,14 @@ func (qb *PerformerStore) getPerformerSort(findFilter *models.FindFilterType) st
sortQuery += getCountSort(performerTable, performersImagesTable, performerIDColumn, direction) sortQuery += getCountSort(performerTable, performersImagesTable, performerIDColumn, direction)
case "galleries_count": case "galleries_count":
sortQuery += getCountSort(performerTable, performersGalleriesTable, performerIDColumn, direction) sortQuery += getCountSort(performerTable, performersGalleriesTable, performerIDColumn, direction)
case "play_count":
sortQuery += qb.sortByPlayCount(direction)
case "o_counter": case "o_counter":
sortQuery += qb.sortByOCounter(direction) sortQuery += qb.sortByOCounter(direction)
case "last_played_at":
sortQuery += qb.sortByLastPlayedAt(direction)
case "last_o_at":
sortQuery += qb.sortByLastOAt(direction)
default: default:
sortQuery += getSort(sort, direction, "performers") sortQuery += getSort(sort, direction, "performers")
} }

View File

@@ -350,7 +350,7 @@ export const PerformerListTable: React.FC<IPerformerListTableProps> = (
}, },
{ {
value: "o_counter", value: "o_counter",
label: intl.formatMessage({ id: "o_counter" }), label: intl.formatMessage({ id: "o_count" }),
defaultShow: true, defaultShow: true,
render: OCounterCell, render: OCounterCell,
}, },

View File

@@ -334,7 +334,7 @@ export const SceneListTable: React.FC<ISceneListTableProps> = (
}, },
{ {
value: "o_counter", value: "o_counter",
label: intl.formatMessage({ id: "o_counter" }), label: intl.formatMessage({ id: "o_count" }),
render: (s) => <>{s.o_counter}</>, render: (s) => <>{s.o_counter}</>,
}, },
{ {

View File

@@ -410,7 +410,7 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
onChange={(value) => setRating(value)} onChange={(value) => setRating(value)}
/> />
<ScrapeDialogRow <ScrapeDialogRow
title={intl.formatMessage({ id: "o_counter" })} title={intl.formatMessage({ id: "o_count" })}
result={oCounter} result={oCounter}
renderOriginalField={() => ( renderOriginalField={() => (
<FormControl <FormControl

View File

@@ -971,7 +971,7 @@
"select_youngest": "Select the youngest file in the duplicate group", "select_youngest": "Select the youngest file in the duplicate group",
"title": "Duplicate Scenes" "title": "Duplicate Scenes"
}, },
"duplicated_phash": "Duplicated (phash)", "duplicated_phash": "Duplicated (pHash)",
"duration": "Duration", "duration": "Duration",
"effect_filters": { "effect_filters": {
"aspect": "Aspect", "aspect": "Aspect",
@@ -1066,7 +1066,7 @@
"index_of_total": "{index} of {total}", "index_of_total": "{index} of {total}",
"instagram": "Instagram", "instagram": "Instagram",
"interactive": "Interactive", "interactive": "Interactive",
"interactive_speed": "Interactive speed", "interactive_speed": "Interactive Speed",
"isMissing": "Is Missing", "isMissing": "Is Missing",
"last_o_at": "Last O At", "last_o_at": "Last O At",
"last_played_at": "Last Played At", "last_played_at": "Last Played At",
@@ -1082,7 +1082,7 @@
"checksum": "Checksum", "checksum": "Checksum",
"downloaded_from": "Downloaded From", "downloaded_from": "Downloaded From",
"hash": "Hash", "hash": "Hash",
"interactive_speed": "Interactive speed", "interactive_speed": "Interactive Speed",
"o_count": "O Count", "o_count": "O Count",
"performer_card": { "performer_card": {
"age": "{age} {years_old}", "age": "{age} {years_old}",
@@ -1102,6 +1102,7 @@
"name": "Name", "name": "Name",
"new": "New", "new": "New",
"none": "None", "none": "None",
"o_count": "O Count",
"o_counter": "O-Counter", "o_counter": "O-Counter",
"o_history": "O History", "o_history": "O History",
"odate_recorded_no": "No O Date Recorded", "odate_recorded_no": "No O Date Recorded",
@@ -1155,7 +1156,7 @@
"penis": "Penis", "penis": "Penis",
"penis_length": "Penis Length", "penis_length": "Penis Length",
"penis_length_cm": "Penis Length (cm)", "penis_length_cm": "Penis Length (cm)",
"perceptual_similarity": "Perceptual Similarity (phash)", "perceptual_similarity": "Perceptual Similarity (pHash)",
"performer": "Performer", "performer": "Performer",
"performer_age": "Performer Age", "performer_age": "Performer Age",
"performer_count": "Performer Count", "performer_count": "Performer Count",

View File

@@ -24,14 +24,14 @@ import { GalleriesCriterionOption } from "./criteria/galleries";
const defaultSortBy = "path"; const defaultSortBy = "path";
const sortByOptions = [ const sortByOptions = ["filesize", "file_count", "date", ...MediaSortByOptions]
"o_counter", .map(ListFilterOptions.createSortBy)
"filesize", .concat([
"file_count", {
"date", messageID: "o_count",
...MediaSortByOptions, value: "o_counter",
].map(ListFilterOptions.createSortBy); },
]);
const displayModeOptions = [DisplayMode.Grid, DisplayMode.Wall]; const displayModeOptions = [DisplayMode.Grid, DisplayMode.Wall];
const criterionOptions = [ const criterionOptions = [
createStringCriterionOption("title"), createStringCriterionOption("title"),
@@ -42,7 +42,7 @@ const criterionOptions = [
PathCriterionOption, PathCriterionOption,
GalleriesCriterionOption, GalleriesCriterionOption,
OrganizedCriterionOption, OrganizedCriterionOption,
createMandatoryNumberCriterionOption("o_counter"), createMandatoryNumberCriterionOption("o_counter", "o_count"),
ResolutionCriterionOption, ResolutionCriterionOption,
OrientationCriterionOption, OrientationCriterionOption,
ImageIsMissingCriterionOption, ImageIsMissingCriterionOption,

View File

@@ -27,6 +27,9 @@ const sortByOptions = [
"random", "random",
"rating", "rating",
"penis_length", "penis_length",
"play_count",
"last_played_at",
"last_o_at",
] ]
.map(ListFilterOptions.createSortBy) .map(ListFilterOptions.createSortBy)
.concat([ .concat([
@@ -43,7 +46,7 @@ const sortByOptions = [
value: "galleries_count", value: "galleries_count",
}, },
{ {
messageID: "o_counter", messageID: "o_count",
value: "o_counter", value: "o_counter",
}, },
]); ]);
@@ -91,7 +94,8 @@ const criterionOptions = [
createMandatoryNumberCriterionOption("scene_count"), createMandatoryNumberCriterionOption("scene_count"),
createMandatoryNumberCriterionOption("image_count"), createMandatoryNumberCriterionOption("image_count"),
createMandatoryNumberCriterionOption("gallery_count"), createMandatoryNumberCriterionOption("gallery_count"),
createMandatoryNumberCriterionOption("o_counter"), createMandatoryNumberCriterionOption("play_count"),
createMandatoryNumberCriterionOption("o_counter", "o_count"),
createBooleanCriterionOption("ignore_auto_tag"), createBooleanCriterionOption("ignore_auto_tag"),
CountryCriterionOption, CountryCriterionOption,
createNumberCriterionOption("height_cm", "height"), createNumberCriterionOption("height_cm", "height"),

View File

@@ -35,7 +35,6 @@ import { OrientationCriterionOption } from "./criteria/orientation";
const defaultSortBy = "date"; const defaultSortBy = "date";
const sortByOptions = [ const sortByOptions = [
"organized", "organized",
"o_counter",
"date", "date",
"file_count", "file_count",
"filesize", "filesize",
@@ -52,8 +51,14 @@ const sortByOptions = [
"interactive_speed", "interactive_speed",
"perceptual_similarity", "perceptual_similarity",
...MediaSortByOptions, ...MediaSortByOptions,
].map(ListFilterOptions.createSortBy); ]
.map(ListFilterOptions.createSortBy)
.concat([
{
messageID: "o_count",
value: "o_counter",
},
]);
const displayModeOptions = [ const displayModeOptions = [
DisplayMode.Grid, DisplayMode.Grid,
DisplayMode.List, DisplayMode.List,
@@ -73,7 +78,7 @@ const criterionOptions = [
DuplicatedCriterionOption, DuplicatedCriterionOption,
OrganizedCriterionOption, OrganizedCriterionOption,
RatingCriterionOption, RatingCriterionOption,
createMandatoryNumberCriterionOption("o_counter"), createMandatoryNumberCriterionOption("o_counter", "o_count"),
ResolutionCriterionOption, ResolutionCriterionOption,
OrientationCriterionOption, OrientationCriterionOption,
createMandatoryNumberCriterionOption("framerate"), createMandatoryNumberCriterionOption("framerate"),