[Files Refactor] Filter and sort by file count (#2744)

* Add filtering on file count
* Add sorting by file count
This commit is contained in:
WithoutPants
2022-07-15 09:29:03 +10:00
parent 5495d72849
commit 461068462c
17 changed files with 85 additions and 14 deletions

View File

@@ -132,6 +132,8 @@ input SceneFilterType {
phash: StringCriterionInput phash: StringCriterionInput
"""Filter by path""" """Filter by path"""
path: StringCriterionInput path: StringCriterionInput
"""Filter by file count"""
file_count: IntCriterionInput
"""Filter by rating""" """Filter by rating"""
rating: IntCriterionInput rating: IntCriterionInput
"""Filter by organized""" """Filter by organized"""
@@ -239,6 +241,8 @@ input GalleryFilterType {
checksum: StringCriterionInput checksum: StringCriterionInput
"""Filter by path""" """Filter by path"""
path: StringCriterionInput path: StringCriterionInput
"""Filter by zip-file count"""
file_count: IntCriterionInput
"""Filter to only include galleries missing this property""" """Filter to only include galleries missing this property"""
is_missing: String is_missing: String
"""Filter to include/exclude galleries that were created from zip""" """Filter to include/exclude galleries that were created from zip"""
@@ -327,6 +331,8 @@ input ImageFilterType {
checksum: StringCriterionInput checksum: StringCriterionInput
"""Filter by path""" """Filter by path"""
path: StringCriterionInput path: StringCriterionInput
"""Filter by file count"""
file_count: IntCriterionInput
"""Filter by rating""" """Filter by rating"""
rating: IntCriterionInput rating: IntCriterionInput
"""Filter by organized""" """Filter by organized"""

View File

@@ -16,6 +16,8 @@ type GalleryFilterType struct {
Checksum *StringCriterionInput `json:"checksum"` Checksum *StringCriterionInput `json:"checksum"`
// Filter by path // Filter by path
Path *StringCriterionInput `json:"path"` Path *StringCriterionInput `json:"path"`
// Filter by zip file count
FileCount *IntCriterionInput `json:"file_count"`
// Filter to only include galleries missing this property // Filter to only include galleries missing this property
IsMissing *string `json:"is_missing"` IsMissing *string `json:"is_missing"`
// Filter to include/exclude galleries that were created from zip // Filter to include/exclude galleries that were created from zip

View File

@@ -11,6 +11,8 @@ type ImageFilterType struct {
Checksum *StringCriterionInput `json:"checksum"` Checksum *StringCriterionInput `json:"checksum"`
// Filter by path // Filter by path
Path *StringCriterionInput `json:"path"` Path *StringCriterionInput `json:"path"`
// Filter by file count
FileCount *IntCriterionInput `json:"file_count"`
// Filter by rating // Filter by rating
Rating *IntCriterionInput `json:"rating"` Rating *IntCriterionInput `json:"rating"`
// Filter by organized // Filter by organized

View File

@@ -26,6 +26,8 @@ type SceneFilterType struct {
Phash *StringCriterionInput `json:"phash"` Phash *StringCriterionInput `json:"phash"`
// Filter by path // Filter by path
Path *StringCriterionInput `json:"path"` Path *StringCriterionInput `json:"path"`
// Filter by file count
FileCount *IntCriterionInput `json:"file_count"`
// Filter by rating // Filter by rating
Rating *IntCriterionInput `json:"rating"` Rating *IntCriterionInput `json:"rating"`
// Filter by organized // Filter by organized

View File

@@ -596,6 +596,7 @@ func (qb *GalleryStore) makeFilter(ctx context.Context, galleryFilter *models.Ga
})) }))
query.handleCriterion(ctx, pathCriterionHandler(galleryFilter.Path, "galleries_query.parent_folder_path", "galleries_query.basename")) query.handleCriterion(ctx, pathCriterionHandler(galleryFilter.Path, "galleries_query.parent_folder_path", "galleries_query.basename"))
query.handleCriterion(ctx, galleryFileCountCriterionHandler(qb, galleryFilter.FileCount))
query.handleCriterion(ctx, intCriterionHandler(galleryFilter.Rating, "galleries.rating")) query.handleCriterion(ctx, intCriterionHandler(galleryFilter.Rating, "galleries.rating"))
query.handleCriterion(ctx, stringCriterionHandler(galleryFilter.URL, "galleries.url")) query.handleCriterion(ctx, stringCriterionHandler(galleryFilter.URL, "galleries.url"))
query.handleCriterion(ctx, boolCriterionHandler(galleryFilter.Organized, "galleries.organized")) query.handleCriterion(ctx, boolCriterionHandler(galleryFilter.Organized, "galleries.organized"))
@@ -683,6 +684,16 @@ func (qb *GalleryStore) QueryCount(ctx context.Context, galleryFilter *models.Ga
return query.executeCount(ctx) return query.executeCount(ctx)
} }
func galleryFileCountCriterionHandler(qb *GalleryStore, fileCount *models.IntCriterionInput) criterionHandlerFunc {
h := countCriterionHandlerBuilder{
primaryTable: galleryTable,
joinTable: galleriesFilesTable,
primaryFK: galleryIDColumn,
}
return h.handler(fileCount)
}
func galleryIsMissingCriterionHandler(qb *GalleryStore, isMissing *string) criterionHandlerFunc { func galleryIsMissingCriterionHandler(qb *GalleryStore, isMissing *string) criterionHandlerFunc {
return func(ctx context.Context, f *filterBuilder) { return func(ctx context.Context, f *filterBuilder) {
if isMissing != nil && *isMissing != "" { if isMissing != nil && *isMissing != "" {
@@ -897,6 +908,8 @@ func (qb *GalleryStore) getGallerySort(findFilter *models.FindFilterType) string
} }
switch sort { switch sort {
case "file_count":
return getCountSort(galleryTable, galleriesFilesTable, galleryIDColumn, direction)
case "images_count": case "images_count":
return getCountSort(galleryTable, galleriesImagesTable, galleryIDColumn, direction) return getCountSort(galleryTable, galleriesImagesTable, galleryIDColumn, direction)
case "tag_count": case "tag_count":

View File

@@ -564,6 +564,7 @@ func (qb *ImageStore) makeFilter(ctx context.Context, imageFilter *models.ImageF
query.handleCriterion(ctx, stringCriterionHandler(imageFilter.Title, "images.title")) query.handleCriterion(ctx, stringCriterionHandler(imageFilter.Title, "images.title"))
query.handleCriterion(ctx, pathCriterionHandler(imageFilter.Path, "images_query.parent_folder_path", "images_query.basename")) query.handleCriterion(ctx, pathCriterionHandler(imageFilter.Path, "images_query.parent_folder_path", "images_query.basename"))
query.handleCriterion(ctx, imageFileCountCriterionHandler(qb, imageFilter.FileCount))
query.handleCriterion(ctx, intCriterionHandler(imageFilter.Rating, "images.rating")) query.handleCriterion(ctx, intCriterionHandler(imageFilter.Rating, "images.rating"))
query.handleCriterion(ctx, intCriterionHandler(imageFilter.OCounter, "images.o_counter")) query.handleCriterion(ctx, intCriterionHandler(imageFilter.OCounter, "images.o_counter"))
query.handleCriterion(ctx, boolCriterionHandler(imageFilter.Organized, "images.organized")) query.handleCriterion(ctx, boolCriterionHandler(imageFilter.Organized, "images.organized"))
@@ -689,6 +690,16 @@ func (qb *ImageStore) QueryCount(ctx context.Context, imageFilter *models.ImageF
return query.executeCount(ctx) return query.executeCount(ctx)
} }
func imageFileCountCriterionHandler(qb *ImageStore, fileCount *models.IntCriterionInput) criterionHandlerFunc {
h := countCriterionHandlerBuilder{
primaryTable: imageTable,
joinTable: imagesFilesTable,
primaryFK: imageIDColumn,
}
return h.handler(fileCount)
}
func imageIsMissingCriterionHandler(qb *ImageStore, isMissing *string) criterionHandlerFunc { func imageIsMissingCriterionHandler(qb *ImageStore, isMissing *string) criterionHandlerFunc {
return func(ctx context.Context, f *filterBuilder) { return func(ctx context.Context, f *filterBuilder) {
if isMissing != nil && *isMissing != "" { if isMissing != nil && *isMissing != "" {
@@ -869,6 +880,8 @@ func (qb *ImageStore) getImageSort(findFilter *models.FindFilterType) string {
switch sort { switch sort {
case "path": case "path":
return " ORDER BY images_query.parent_folder_path " + direction + ", images_query.basename " + direction return " ORDER BY images_query.parent_folder_path " + direction + ", images_query.basename " + direction
case "file_count":
return getCountSort(imageTable, imagesFilesTable, imageIDColumn, direction)
case "tag_count": case "tag_count":
return getCountSort(imageTable, imagesTagsTable, imageIDColumn, direction) return getCountSort(imageTable, imagesTagsTable, imageIDColumn, direction)
case "performer_count": case "performer_count":

View File

@@ -756,6 +756,7 @@ func (qb *SceneStore) makeFilter(ctx context.Context, sceneFilter *models.SceneF
} }
query.handleCriterion(ctx, pathCriterionHandler(sceneFilter.Path, "scenes_query.parent_folder_path", "scenes_query.basename")) query.handleCriterion(ctx, pathCriterionHandler(sceneFilter.Path, "scenes_query.parent_folder_path", "scenes_query.basename"))
query.handleCriterion(ctx, sceneFileCountCriterionHandler(qb, sceneFilter.FileCount))
query.handleCriterion(ctx, stringCriterionHandler(sceneFilter.Title, "scenes.title")) query.handleCriterion(ctx, stringCriterionHandler(sceneFilter.Title, "scenes.title"))
query.handleCriterion(ctx, stringCriterionHandler(sceneFilter.Details, "scenes.details")) query.handleCriterion(ctx, stringCriterionHandler(sceneFilter.Details, "scenes.details"))
query.handleCriterion(ctx, criterionHandlerFunc(func(ctx context.Context, f *filterBuilder) { query.handleCriterion(ctx, criterionHandlerFunc(func(ctx context.Context, f *filterBuilder) {
@@ -916,6 +917,16 @@ func (qb *SceneStore) queryGroupedFields(ctx context.Context, options models.Sce
return ret, nil return ret, nil
} }
func sceneFileCountCriterionHandler(qb *SceneStore, fileCount *models.IntCriterionInput) criterionHandlerFunc {
h := countCriterionHandlerBuilder{
primaryTable: sceneTable,
joinTable: scenesFilesTable,
primaryFK: sceneIDColumn,
}
return h.handler(fileCount)
}
func scenePhashDuplicatedCriterionHandler(duplicatedFilter *models.PHashDuplicationCriterionInput) criterionHandlerFunc { func scenePhashDuplicatedCriterionHandler(duplicatedFilter *models.PHashDuplicationCriterionInput) criterionHandlerFunc {
return func(ctx context.Context, f *filterBuilder) { return func(ctx context.Context, f *filterBuilder) {
// TODO: Wishlist item: Implement Distance matching // TODO: Wishlist item: Implement Distance matching
@@ -1204,6 +1215,8 @@ func (qb *SceneStore) setSceneSort(query *queryBuilder, findFilter *models.FindF
query.sortAndPagination += getCountSort(sceneTable, scenesTagsTable, sceneIDColumn, direction) query.sortAndPagination += getCountSort(sceneTable, scenesTagsTable, sceneIDColumn, direction)
case "performer_count": case "performer_count":
query.sortAndPagination += getCountSort(sceneTable, performersScenesTable, sceneIDColumn, direction) query.sortAndPagination += getCountSort(sceneTable, performersScenesTable, sceneIDColumn, direction)
case "file_count":
query.sortAndPagination += getCountSort(sceneTable, scenesFilesTable, sceneIDColumn, direction)
case "path": case "path":
// special handling for path // special handling for path
query.sortAndPagination += fmt.Sprintf(" ORDER BY scenes_query.parent_folder_path %s, scenes_query.basename %[1]s", direction) query.sortAndPagination += fmt.Sprintf(" ORDER BY scenes_query.parent_folder_path %s, scenes_query.basename %[1]s", direction)

View File

@@ -7,14 +7,15 @@ After migrating, please run a scan on your entire library to populate missing da
Please report all issues to the following Github issue: https://github.com/stashapp/stash/issues/2737 Please report all issues to the following Github issue: https://github.com/stashapp/stash/issues/2737
### 💥 Known issues ### 💥 Known issues
* import/export functionality is currently disabled. Needs further design. * Import/export functionality is currently disabled. Needs further design.
* missing covers are not currently regenerated. Need to consider further, especially around scene cover redesign. * Missing covers are not currently regenerated. Need to consider further, especially around scene cover redesign.
* deleting galleries is currently slow. * Deleting galleries is currently slow.
* Don't include file extension as part of the title scan flag is not supported. * Don't include file extension as part of the title scan flag is not supported.
* Set name, date, details from embedded file metadata scan flag is not supported. * Set name, date, details from embedded file metadata scan flag is not supported.
### ✨ New Features ### ✨ New Features
* Added support for identical files. Identical files are assigned to the same scene/gallery/image and can be viewed in File Info. ([#2676](https://github.com/stashapp/stash/pull/2676)) * Added support for identical files. Identical files are assigned to the same scene/gallery/image and can be viewed in File Info. ([#2676](https://github.com/stashapp/stash/pull/2676))
* Added support for filtering and sorting by file count. ([#2744](https://github.com/stashapp/stash/pull/2744))
* Added release notes dialog. ([#2726](https://github.com/stashapp/stash/pull/2726)) * Added release notes dialog. ([#2726](https://github.com/stashapp/stash/pull/2726))
### 🎨 Improvements ### 🎨 Improvements

View File

@@ -10,7 +10,7 @@ interface IReleaseNotes {
export const releaseNotes: IReleaseNotes[] = [ export const releaseNotes: IReleaseNotes[] = [
{ {
date: 20220707, date: 20220715,
content: v0170, content: v0170,
}, },
]; ];

View File

@@ -7,12 +7,13 @@ After migrating, please run a scan on your entire library to populate missing da
Please report all issues to the following Github issue: https://github.com/stashapp/stash/issues/2737 Please report all issues to the following Github issue: https://github.com/stashapp/stash/issues/2737
### 💥 Known issues ### 💥 Known issues
* import/export functionality is currently disabled. Needs further design. * Import/export functionality is currently disabled. Needs further design.
* missing covers are not currently regenerated. Need to consider further, especially around scene cover redesign. * Missing covers are not currently regenerated. Need to consider further, especially around scene cover redesign.
* deleting galleries is currently slow. * Deleting galleries is currently slow.
* Don't include file extension as part of the title scan flag is not supported. * Don't include file extension as part of the title scan flag is not supported.
* Set name, date, details from embedded file metadata scan flag is not supported. * Set name, date, details from embedded file metadata scan flag is not supported.
### Other changes: ### Other changes:
* Added support for filtering and sorting by file count. ([#2744](https://github.com/stashapp/stash/pull/2744))
* Changelog has been moved from the stats page to a section in the Settings page. * Changelog has been moved from the stats page to a section in the Settings page.

View File

@@ -728,6 +728,7 @@
"false": "False", "false": "False",
"favourite": "Favourite", "favourite": "Favourite",
"file": "file", "file": "file",
"file_count": "File Count",
"file_info": "File Info", "file_info": "File Info",
"file_mod_time": "File Modification Time", "file_mod_time": "File Modification Time",
"files": "files", "files": "files",
@@ -1024,5 +1025,6 @@
"videos": "Videos", "videos": "Videos",
"view_all": "View All", "view_all": "View All",
"weight": "Weight", "weight": "Weight",
"years_old": "years old" "years_old": "years old",
"zip_file_count": "Zip File Count"
} }

View File

@@ -497,8 +497,11 @@ export class MandatoryNumberCriterionOption extends CriterionOption {
} }
} }
export function createMandatoryNumberCriterionOption(value: CriterionType) { export function createMandatoryNumberCriterionOption(
return new MandatoryNumberCriterionOption(value, value, value); value: CriterionType,
messageID?: string
) {
return new MandatoryNumberCriterionOption(messageID ?? value, value, value);
} }
export class DurationCriterion extends Criterion<INumberValue> { export class DurationCriterion extends Criterion<INumberValue> {

View File

@@ -75,6 +75,7 @@ export function makeCriteria(type: CriterionType = "none") {
case "performer_count": case "performer_count":
case "performer_age": case "performer_age":
case "tag_count": case "tag_count":
case "file_count":
return new NumberCriterion( return new NumberCriterion(
new MandatoryNumberCriterionOption(type, type) new MandatoryNumberCriterionOption(type, type)
); );

View File

@@ -25,6 +25,10 @@ const sortByOptions = ["date", ...MediaSortByOptions]
messageID: "image_count", messageID: "image_count",
value: "images_count", value: "images_count",
}, },
{
messageID: "zip_file_count",
value: "file_count",
},
]); ]);
const displayModeOptions = [ const displayModeOptions = [
@@ -56,6 +60,7 @@ const criterionOptions = [
createStringCriterionOption("image_count"), createStringCriterionOption("image_count"),
StudiosCriterionOption, StudiosCriterionOption,
createStringCriterionOption("url"), createStringCriterionOption("url"),
createMandatoryNumberCriterionOption("file_count", "zip_file_count"),
]; ];
export const GalleryListFilterOptions = new ListFilterOptions( export const GalleryListFilterOptions = new ListFilterOptions(

View File

@@ -19,9 +19,12 @@ import { DisplayMode } from "./types";
const defaultSortBy = "path"; const defaultSortBy = "path";
const sortByOptions = ["o_counter", "filesize", ...MediaSortByOptions].map( const sortByOptions = [
ListFilterOptions.createSortBy "o_counter",
); "filesize",
"file_count",
...MediaSortByOptions,
].map(ListFilterOptions.createSortBy);
const displayModeOptions = [DisplayMode.Grid, DisplayMode.Wall]; const displayModeOptions = [DisplayMode.Grid, DisplayMode.Wall];
const criterionOptions = [ const criterionOptions = [
@@ -41,6 +44,7 @@ const criterionOptions = [
createMandatoryNumberCriterionOption("performer_age"), createMandatoryNumberCriterionOption("performer_age"),
PerformerFavoriteCriterionOption, PerformerFavoriteCriterionOption,
StudiosCriterionOption, StudiosCriterionOption,
createMandatoryNumberCriterionOption("file_count"),
]; ];
export const ImageListFilterOptions = new ListFilterOptions( export const ImageListFilterOptions = new ListFilterOptions(
defaultSortBy, defaultSortBy,

View File

@@ -30,6 +30,7 @@ const sortByOptions = [
"organized", "organized",
"o_counter", "o_counter",
"date", "date",
"file_count",
"filesize", "filesize",
"duration", "duration",
"framerate", "framerate",
@@ -81,6 +82,7 @@ const criterionOptions = [
InteractiveCriterionOption, InteractiveCriterionOption,
CaptionsCriterionOption, CaptionsCriterionOption,
createMandatoryNumberCriterionOption("interactive_speed"), createMandatoryNumberCriterionOption("interactive_speed"),
createMandatoryNumberCriterionOption("file_count"),
]; ];
export const SceneListFilterOptions = new ListFilterOptions( export const SceneListFilterOptions = new ListFilterOptions(

View File

@@ -133,4 +133,5 @@ export type CriterionType =
| "performer_favorite" | "performer_favorite"
| "performer_age" | "performer_age"
| "duplicated" | "duplicated"
| "ignore_auto_tag"; | "ignore_auto_tag"
| "file_count";