mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 08:26:00 +01:00
Related files/folder filter for scenes/images/galleries (#6158)
* Add related files filter to scene filter * Add files_filter to gallery filter * Add files_filter to image filter * Add gallery related folder filter
This commit is contained in:
parent
04fcf6f512
commit
a50a0d4289
12 changed files with 129 additions and 20 deletions
|
|
@ -330,6 +330,8 @@ input SceneFilterType {
|
|||
groups_filter: GroupFilterType
|
||||
"Filter by related markers that meet this criteria"
|
||||
markers_filter: SceneMarkerFilterType
|
||||
"Filter by related files that meet this criteria"
|
||||
files_filter: FileFilterType
|
||||
}
|
||||
|
||||
input MovieFilterType {
|
||||
|
|
@ -534,6 +536,10 @@ input GalleryFilterType {
|
|||
studios_filter: StudioFilterType
|
||||
"Filter by related tags that meet this criteria"
|
||||
tags_filter: TagFilterType
|
||||
"Filter by related files that meet this criteria"
|
||||
files_filter: FileFilterType
|
||||
"Filter by related folders that meet this criteria"
|
||||
folders_filter: FolderFilterType
|
||||
}
|
||||
|
||||
input TagFilterType {
|
||||
|
|
@ -679,6 +685,8 @@ input ImageFilterType {
|
|||
studios_filter: StudioFilterType
|
||||
"Filter by related tags that meet this criteria"
|
||||
tags_filter: TagFilterType
|
||||
"Filter by related files that meet this criteria"
|
||||
files_filter: FileFilterType
|
||||
}
|
||||
|
||||
input FileFilterType {
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@ type GalleryFilterType struct {
|
|||
StudiosFilter *StudioFilterType `json:"studios_filter"`
|
||||
// Filter by related tags that meet this criteria
|
||||
TagsFilter *TagFilterType `json:"tags_filter"`
|
||||
// Filter by related files that meet this criteria
|
||||
FilesFilter *FileFilterType `json:"files_filter"`
|
||||
// Filter by related folders that meet this criteria
|
||||
FoldersFilter *FolderFilterType `json:"folders_filter"`
|
||||
// Filter by created at
|
||||
CreatedAt *TimestampCriterionInput `json:"created_at"`
|
||||
// Filter by updated at
|
||||
|
|
|
|||
|
|
@ -57,6 +57,8 @@ type ImageFilterType struct {
|
|||
StudiosFilter *StudioFilterType `json:"studios_filter"`
|
||||
// Filter by related tags that meet this criteria
|
||||
TagsFilter *TagFilterType `json:"tags_filter"`
|
||||
// Filter by related files that meet this criteria
|
||||
FilesFilter *FileFilterType `json:"files_filter"`
|
||||
// Filter by created at
|
||||
CreatedAt *TimestampCriterionInput `json:"created_at"`
|
||||
// Filter by updated at
|
||||
|
|
|
|||
|
|
@ -111,6 +111,8 @@ type SceneFilterType struct {
|
|||
MoviesFilter *GroupFilterType `json:"movies_filter"`
|
||||
// Filter by related markers that meet this criteria
|
||||
MarkersFilter *SceneMarkerFilterType `json:"markers_filter"`
|
||||
// Filter by related files that meet this criteria
|
||||
FilesFilter *FileFilterType `json:"files_filter"`
|
||||
// Filter by created at
|
||||
CreatedAt *TimestampCriterionInput `json:"created_at"`
|
||||
// Filter by updated at
|
||||
|
|
|
|||
|
|
@ -1041,6 +1041,7 @@ type relatedFilterHandler struct {
|
|||
relatedRepo repository
|
||||
relatedHandler criterionHandler
|
||||
joinFn func(f *filterBuilder)
|
||||
directJoin bool
|
||||
}
|
||||
|
||||
func (h *relatedFilterHandler) handle(ctx context.Context, f *filterBuilder) {
|
||||
|
|
@ -1054,6 +1055,16 @@ func (h *relatedFilterHandler) handle(ctx context.Context, f *filterBuilder) {
|
|||
return
|
||||
}
|
||||
|
||||
if h.joinFn != nil {
|
||||
h.joinFn(f)
|
||||
}
|
||||
|
||||
if h.directJoin {
|
||||
// rerun handler using existing filter builder
|
||||
h.relatedHandler.handle(ctx, f)
|
||||
return
|
||||
}
|
||||
|
||||
subQuery := h.relatedRepo.newQuery()
|
||||
selectIDs(&subQuery, subQuery.repository.tableName)
|
||||
if err := subQuery.addFilter(ff); err != nil {
|
||||
|
|
@ -1061,9 +1072,5 @@ func (h *relatedFilterHandler) handle(ctx context.Context, f *filterBuilder) {
|
|||
return
|
||||
}
|
||||
|
||||
if h.joinFn != nil {
|
||||
h.joinFn(f)
|
||||
}
|
||||
|
||||
f.addWhere(fmt.Sprintf("%s IN ("+subQuery.toSQL(false)+")", h.relatedIDCol), subQuery.args...)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -285,7 +285,7 @@ type fileRepositoryType struct {
|
|||
var (
|
||||
fileRepository = fileRepositoryType{
|
||||
repository: repository{
|
||||
tableName: sceneTable,
|
||||
tableName: fileTable,
|
||||
idColumn: idColumn,
|
||||
},
|
||||
scenes: joinRepository{
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import (
|
|||
|
||||
type fileFilterHandler struct {
|
||||
fileFilter *models.FileFilterType
|
||||
// if true, don't allow use of related filters
|
||||
isRelated bool
|
||||
}
|
||||
|
||||
func (qb *fileFilterHandler) validate() error {
|
||||
|
|
@ -22,8 +24,12 @@ func (qb *fileFilterHandler) validate() error {
|
|||
return err
|
||||
}
|
||||
|
||||
if qb.isRelated && (fileFilter.ScenesFilter != nil || fileFilter.ImagesFilter != nil || fileFilter.GalleriesFilter != nil) {
|
||||
return fmt.Errorf("cannot use related filters inside a related filter")
|
||||
}
|
||||
|
||||
if subFilter := fileFilter.SubFilter(); subFilter != nil {
|
||||
sqb := &fileFilterHandler{fileFilter: subFilter}
|
||||
sqb := &fileFilterHandler{fileFilter: subFilter, isRelated: qb.isRelated}
|
||||
if err := sqb.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -45,7 +51,7 @@ func (qb *fileFilterHandler) handle(ctx context.Context, f *filterBuilder) {
|
|||
|
||||
sf := fileFilter.SubFilter()
|
||||
if sf != nil {
|
||||
sub := &fileFilterHandler{sf}
|
||||
sub := &fileFilterHandler{sf, qb.isRelated}
|
||||
handleSubFilter(ctx, sub, f, fileFilter.OperatorFilter)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import (
|
|||
|
||||
type folderFilterHandler struct {
|
||||
folderFilter *models.FolderFilterType
|
||||
table sqlTable
|
||||
isRelated bool
|
||||
}
|
||||
|
||||
func (qb *folderFilterHandler) validate() error {
|
||||
|
|
@ -21,8 +23,12 @@ func (qb *folderFilterHandler) validate() error {
|
|||
return err
|
||||
}
|
||||
|
||||
if qb.isRelated && (folderFilter.GalleriesFilter != nil) {
|
||||
return fmt.Errorf("cannot use related filters inside a related filter")
|
||||
}
|
||||
|
||||
if subFilter := folderFilter.SubFilter(); subFilter != nil {
|
||||
sqb := &folderFilterHandler{folderFilter: subFilter}
|
||||
sqb := &folderFilterHandler{folderFilter: subFilter, isRelated: qb.isRelated}
|
||||
if err := sqb.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -44,7 +50,7 @@ func (qb *folderFilterHandler) handle(ctx context.Context, f *filterBuilder) {
|
|||
|
||||
sf := folderFilter.SubFilter()
|
||||
if sf != nil {
|
||||
sub := &folderFilterHandler{sf}
|
||||
sub := &folderFilterHandler{folderFilter: sf, table: qb.table}
|
||||
handleSubFilter(ctx, sub, f, folderFilter.OperatorFilter)
|
||||
}
|
||||
|
||||
|
|
@ -52,25 +58,29 @@ func (qb *folderFilterHandler) handle(ctx context.Context, f *filterBuilder) {
|
|||
}
|
||||
|
||||
func (qb *folderFilterHandler) criterionHandler() criterionHandler {
|
||||
if qb.table == "" {
|
||||
qb.table = folderTable
|
||||
}
|
||||
|
||||
folderFilter := qb.folderFilter
|
||||
return compoundHandler{
|
||||
stringCriterionHandler(folderFilter.Path, "folders.path"),
|
||||
×tampCriterionHandler{folderFilter.ModTime, "folders.mod_time", nil},
|
||||
stringCriterionHandler(folderFilter.Path, qb.table.Col("path")),
|
||||
×tampCriterionHandler{folderFilter.ModTime, qb.table.Col("mod_time"), nil},
|
||||
|
||||
qb.parentFolderCriterionHandler(folderFilter.ParentFolder),
|
||||
qb.zipFileCriterionHandler(folderFilter.ZipFile),
|
||||
|
||||
qb.galleryCountCriterionHandler(folderFilter.GalleryCount),
|
||||
|
||||
×tampCriterionHandler{folderFilter.CreatedAt, "folders.created_at", nil},
|
||||
×tampCriterionHandler{folderFilter.UpdatedAt, "folders.updated_at", nil},
|
||||
×tampCriterionHandler{folderFilter.CreatedAt, qb.table.Col("created_at"), nil},
|
||||
×tampCriterionHandler{folderFilter.UpdatedAt, qb.table.Col("updated_at"), nil},
|
||||
|
||||
&relatedFilterHandler{
|
||||
relatedIDCol: "galleries.id",
|
||||
relatedIDCol: qb.table.Col("id"),
|
||||
relatedRepo: galleryRepository.repository,
|
||||
relatedHandler: &galleryFilterHandler{folderFilter.GalleriesFilter},
|
||||
joinFn: func(f *filterBuilder) {
|
||||
folderRepository.galleries.innerJoin(f, "", "folders.id")
|
||||
folderRepository.galleries.innerJoin(f, "", qb.table.Col("id"))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -85,7 +95,7 @@ func (qb *folderFilterHandler) zipFileCriterionHandler(criterion *models.MultiCr
|
|||
notClause = "NOT"
|
||||
}
|
||||
|
||||
f.addWhere(fmt.Sprintf("folders.zip_file_id IS %s NULL", notClause))
|
||||
f.addWhere(fmt.Sprintf("%s.zip_file_id IS %s NULL", qb.table.Name(), notClause))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -102,9 +112,9 @@ func (qb *folderFilterHandler) zipFileCriterionHandler(criterion *models.MultiCr
|
|||
havingClause := ""
|
||||
switch criterion.Modifier {
|
||||
case models.CriterionModifierIncludes:
|
||||
whereClause = "folders.zip_file_id IN " + getInBinding(len(criterion.Value))
|
||||
whereClause = fmt.Sprintf("%s.zip_file_id IN %s", qb.table.Name(), getInBinding(len(criterion.Value)))
|
||||
case models.CriterionModifierExcludes:
|
||||
whereClause = "folders.zip_file_id NOT IN " + getInBinding(len(criterion.Value))
|
||||
whereClause = fmt.Sprintf("%s.zip_file_id NOT IN %s", qb.table.Name(), getInBinding(len(criterion.Value)))
|
||||
}
|
||||
|
||||
f.addWhere(whereClause, args...)
|
||||
|
|
@ -128,8 +138,8 @@ func (qb *folderFilterHandler) parentFolderCriterionHandler(folder *models.Hiera
|
|||
}
|
||||
|
||||
hh := hierarchicalMultiCriterionHandlerBuilder{
|
||||
primaryTable: folderTable,
|
||||
foreignTable: folderTable,
|
||||
primaryTable: qb.table.Name(),
|
||||
foreignTable: qb.table.Name(),
|
||||
foreignFK: "parent_folder_id",
|
||||
parentFK: "parent_folder_id",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,6 +146,36 @@ func (qb *galleryFilterHandler) criterionHandler() criterionHandler {
|
|||
galleryRepository.tags.innerJoin(f, "gallery_tag", "galleries.id")
|
||||
},
|
||||
},
|
||||
|
||||
&relatedFilterHandler{
|
||||
relatedIDCol: "files.id",
|
||||
relatedRepo: fileRepository.repository,
|
||||
relatedHandler: &fileFilterHandler{
|
||||
fileFilter: filter.FilesFilter,
|
||||
isRelated: true,
|
||||
},
|
||||
joinFn: func(f *filterBuilder) {
|
||||
galleryRepository.addFilesTable(f)
|
||||
galleryRepository.addFoldersTable(f)
|
||||
},
|
||||
// don't use a subquery; join directly
|
||||
directJoin: true,
|
||||
},
|
||||
|
||||
&relatedFilterHandler{
|
||||
relatedIDCol: "gallery_folder.id",
|
||||
relatedRepo: folderRepository.repository,
|
||||
relatedHandler: &folderFilterHandler{
|
||||
folderFilter: filter.FoldersFilter,
|
||||
table: "gallery_folder",
|
||||
isRelated: true,
|
||||
},
|
||||
joinFn: func(f *filterBuilder) {
|
||||
f.addLeftJoin(folderTable, "gallery_folder", "galleries.folder_id = gallery_folder.id")
|
||||
},
|
||||
// don't use a subquery; join directly
|
||||
directJoin: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -123,6 +123,21 @@ func (qb *imageFilterHandler) criterionHandler() criterionHandler {
|
|||
imageRepository.tags.innerJoin(f, "image_tag", "images.id")
|
||||
},
|
||||
},
|
||||
|
||||
&relatedFilterHandler{
|
||||
relatedIDCol: "files.id",
|
||||
relatedRepo: fileRepository.repository,
|
||||
relatedHandler: &fileFilterHandler{
|
||||
fileFilter: imageFilter.FilesFilter,
|
||||
isRelated: true,
|
||||
},
|
||||
joinFn: func(f *filterBuilder) {
|
||||
imageRepository.addFilesTable(f)
|
||||
imageRepository.addFoldersTable(f)
|
||||
},
|
||||
// don't use a subquery; join directly
|
||||
directJoin: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -202,6 +202,21 @@ func (qb *sceneFilterHandler) criterionHandler() criterionHandler {
|
|||
},
|
||||
},
|
||||
|
||||
&relatedFilterHandler{
|
||||
relatedIDCol: "files.id",
|
||||
relatedRepo: fileRepository.repository,
|
||||
relatedHandler: &fileFilterHandler{
|
||||
fileFilter: sceneFilter.FilesFilter,
|
||||
isRelated: true,
|
||||
},
|
||||
joinFn: func(f *filterBuilder) {
|
||||
qb.addFilesTable(f)
|
||||
qb.addFoldersTable(f)
|
||||
},
|
||||
// don't use a subquery; join directly
|
||||
directJoin: true,
|
||||
},
|
||||
|
||||
&relatedFilterHandler{
|
||||
relatedIDCol: "scene_markers.id",
|
||||
relatedRepo: sceneMarkerRepository.repository,
|
||||
|
|
|
|||
|
|
@ -362,3 +362,13 @@ func coalesce(column string) string {
|
|||
func like(v string) string {
|
||||
return "%" + v + "%"
|
||||
}
|
||||
|
||||
type sqlTable string
|
||||
|
||||
func (t sqlTable) Name() string {
|
||||
return string(t)
|
||||
}
|
||||
|
||||
func (t sqlTable) Col(n string) string {
|
||||
return fmt.Sprintf("%s.%s", string(t), n)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue