mirror of https://github.com/stashapp/stash.git
525 lines
15 KiB
Go
525 lines
15 KiB
Go
package sqlite
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"strconv"
|
|
|
|
"github.com/stashapp/stash/pkg/models"
|
|
)
|
|
|
|
const galleryTable = "galleries"
|
|
|
|
const performersGalleriesTable = "performers_galleries"
|
|
const galleriesTagsTable = "galleries_tags"
|
|
const galleriesImagesTable = "galleries_images"
|
|
const galleriesScenesTable = "scenes_galleries"
|
|
const galleryIDColumn = "gallery_id"
|
|
|
|
type galleryQueryBuilder struct {
|
|
repository
|
|
}
|
|
|
|
func NewGalleryReaderWriter(tx dbi) *galleryQueryBuilder {
|
|
return &galleryQueryBuilder{
|
|
repository{
|
|
tx: tx,
|
|
tableName: galleryTable,
|
|
idColumn: idColumn,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) Create(newObject models.Gallery) (*models.Gallery, error) {
|
|
var ret models.Gallery
|
|
if err := qb.insertObject(newObject, &ret); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &ret, nil
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) Update(updatedObject models.Gallery) (*models.Gallery, error) {
|
|
const partial = false
|
|
if err := qb.update(updatedObject.ID, updatedObject, partial); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return qb.Find(updatedObject.ID)
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) UpdatePartial(updatedObject models.GalleryPartial) (*models.Gallery, error) {
|
|
const partial = true
|
|
if err := qb.update(updatedObject.ID, updatedObject, partial); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return qb.Find(updatedObject.ID)
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) UpdateChecksum(id int, checksum string) error {
|
|
return qb.updateMap(id, map[string]interface{}{
|
|
"checksum": checksum,
|
|
})
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) UpdateFileModTime(id int, modTime models.NullSQLiteTimestamp) error {
|
|
return qb.updateMap(id, map[string]interface{}{
|
|
"file_mod_time": modTime,
|
|
})
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) Destroy(id int) error {
|
|
return qb.destroyExisting([]int{id})
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) Find(id int) (*models.Gallery, error) {
|
|
var ret models.Gallery
|
|
if err := qb.get(id, &ret); err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
return &ret, nil
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) FindMany(ids []int) ([]*models.Gallery, error) {
|
|
var galleries []*models.Gallery
|
|
for _, id := range ids {
|
|
gallery, err := qb.Find(id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if gallery == nil {
|
|
return nil, fmt.Errorf("gallery with id %d not found", id)
|
|
}
|
|
|
|
galleries = append(galleries, gallery)
|
|
}
|
|
|
|
return galleries, nil
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) FindByChecksum(checksum string) (*models.Gallery, error) {
|
|
query := "SELECT * FROM galleries WHERE checksum = ? LIMIT 1"
|
|
args := []interface{}{checksum}
|
|
return qb.queryGallery(query, args)
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) FindByChecksums(checksums []string) ([]*models.Gallery, error) {
|
|
query := "SELECT * FROM galleries WHERE checksum IN " + getInBinding(len(checksums))
|
|
var args []interface{}
|
|
for _, checksum := range checksums {
|
|
args = append(args, checksum)
|
|
}
|
|
return qb.queryGalleries(query, args)
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) FindByPath(path string) (*models.Gallery, error) {
|
|
query := "SELECT * FROM galleries WHERE path = ? LIMIT 1"
|
|
args := []interface{}{path}
|
|
return qb.queryGallery(query, args)
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) FindBySceneID(sceneID int) ([]*models.Gallery, error) {
|
|
query := selectAll(galleryTable) + `
|
|
LEFT JOIN scenes_galleries as scenes_join on scenes_join.gallery_id = galleries.id
|
|
WHERE scenes_join.scene_id = ?
|
|
GROUP BY galleries.id
|
|
`
|
|
args := []interface{}{sceneID}
|
|
return qb.queryGalleries(query, args)
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) FindByImageID(imageID int) ([]*models.Gallery, error) {
|
|
query := selectAll(galleryTable) + `
|
|
LEFT JOIN galleries_images as images_join on images_join.gallery_id = galleries.id
|
|
WHERE images_join.image_id = ?
|
|
GROUP BY galleries.id
|
|
`
|
|
args := []interface{}{imageID}
|
|
return qb.queryGalleries(query, args)
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) CountByImageID(imageID int) (int, error) {
|
|
query := `SELECT image_id FROM galleries_images
|
|
WHERE image_id = ?
|
|
GROUP BY gallery_id`
|
|
args := []interface{}{imageID}
|
|
return qb.runCountQuery(qb.buildCountQuery(query), args)
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) Count() (int, error) {
|
|
return qb.runCountQuery(qb.buildCountQuery("SELECT galleries.id FROM galleries"), nil)
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) All() ([]*models.Gallery, error) {
|
|
return qb.queryGalleries(selectAll("galleries")+qb.getGallerySort(nil), nil)
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) makeQuery(galleryFilter *models.GalleryFilterType, findFilter *models.FindFilterType) queryBuilder {
|
|
if galleryFilter == nil {
|
|
galleryFilter = &models.GalleryFilterType{}
|
|
}
|
|
if findFilter == nil {
|
|
findFilter = &models.FindFilterType{}
|
|
}
|
|
|
|
query := qb.newQuery()
|
|
|
|
query.body = selectDistinctIDs("galleries")
|
|
query.body += `
|
|
left join performers_galleries as performers_join on performers_join.gallery_id = galleries.id
|
|
left join scenes_galleries as scenes_join on scenes_join.gallery_id = galleries.id
|
|
left join studios as studio on studio.id = galleries.studio_id
|
|
left join galleries_tags as tags_join on tags_join.gallery_id = galleries.id
|
|
left join galleries_images as images_join on images_join.gallery_id = galleries.id
|
|
left join images on images_join.image_id = images.id
|
|
`
|
|
|
|
if q := findFilter.Q; q != nil && *q != "" {
|
|
searchColumns := []string{"galleries.title", "galleries.path", "galleries.checksum"}
|
|
clause, thisArgs := getSearchBinding(searchColumns, *q, false)
|
|
query.addWhere(clause)
|
|
query.addArg(thisArgs...)
|
|
}
|
|
|
|
if zipFilter := galleryFilter.IsZip; zipFilter != nil {
|
|
var favStr string
|
|
if *zipFilter == true {
|
|
favStr = "1"
|
|
} else {
|
|
favStr = "0"
|
|
}
|
|
query.addWhere("galleries.zip = " + favStr)
|
|
}
|
|
|
|
query.handleStringCriterionInput(galleryFilter.Path, "galleries.path")
|
|
query.handleIntCriterionInput(galleryFilter.Rating, "galleries.rating")
|
|
query.handleStringCriterionInput(galleryFilter.URL, "galleries.url")
|
|
qb.handleAverageResolutionFilter(&query, galleryFilter.AverageResolution)
|
|
|
|
if Organized := galleryFilter.Organized; Organized != nil {
|
|
var organized string
|
|
if *Organized == true {
|
|
organized = "1"
|
|
} else {
|
|
organized = "0"
|
|
}
|
|
query.addWhere("galleries.organized = " + organized)
|
|
}
|
|
|
|
if isMissingFilter := galleryFilter.IsMissing; isMissingFilter != nil && *isMissingFilter != "" {
|
|
switch *isMissingFilter {
|
|
case "scenes":
|
|
query.addWhere("scenes_join.gallery_id IS NULL")
|
|
case "studio":
|
|
query.addWhere("galleries.studio_id IS NULL")
|
|
case "performers":
|
|
query.addWhere("performers_join.gallery_id IS NULL")
|
|
case "date":
|
|
query.addWhere("galleries.date IS \"\" OR galleries.date IS \"0001-01-01\"")
|
|
case "tags":
|
|
query.addWhere("tags_join.gallery_id IS NULL")
|
|
default:
|
|
query.addWhere("galleries." + *isMissingFilter + " IS NULL")
|
|
}
|
|
}
|
|
|
|
if tagsFilter := galleryFilter.Tags; tagsFilter != nil && len(tagsFilter.Value) > 0 {
|
|
for _, tagID := range tagsFilter.Value {
|
|
query.addArg(tagID)
|
|
}
|
|
|
|
query.body += " LEFT JOIN tags on tags_join.tag_id = tags.id"
|
|
whereClause, havingClause := getMultiCriterionClause("galleries", "tags", "galleries_tags", "gallery_id", "tag_id", tagsFilter)
|
|
query.addWhere(whereClause)
|
|
query.addHaving(havingClause)
|
|
}
|
|
|
|
if tagCountFilter := galleryFilter.TagCount; tagCountFilter != nil {
|
|
clause, count := getCountCriterionClause(galleryTable, galleriesTagsTable, galleryIDColumn, *tagCountFilter)
|
|
|
|
if count == 1 {
|
|
query.addArg(tagCountFilter.Value)
|
|
}
|
|
|
|
query.addWhere(clause)
|
|
}
|
|
|
|
if performersFilter := galleryFilter.Performers; performersFilter != nil && len(performersFilter.Value) > 0 {
|
|
for _, performerID := range performersFilter.Value {
|
|
query.addArg(performerID)
|
|
}
|
|
|
|
query.body += " LEFT JOIN performers ON performers_join.performer_id = performers.id"
|
|
whereClause, havingClause := getMultiCriterionClause("galleries", "performers", "performers_galleries", "gallery_id", "performer_id", performersFilter)
|
|
query.addWhere(whereClause)
|
|
query.addHaving(havingClause)
|
|
}
|
|
|
|
if performerCountFilter := galleryFilter.PerformerCount; performerCountFilter != nil {
|
|
clause, count := getCountCriterionClause(galleryTable, performersGalleriesTable, galleryIDColumn, *performerCountFilter)
|
|
|
|
if count == 1 {
|
|
query.addArg(performerCountFilter.Value)
|
|
}
|
|
|
|
query.addWhere(clause)
|
|
}
|
|
|
|
if studiosFilter := galleryFilter.Studios; studiosFilter != nil && len(studiosFilter.Value) > 0 {
|
|
for _, studioID := range studiosFilter.Value {
|
|
query.addArg(studioID)
|
|
}
|
|
|
|
whereClause, havingClause := getMultiCriterionClause("galleries", "studio", "", "", "studio_id", studiosFilter)
|
|
query.addWhere(whereClause)
|
|
query.addHaving(havingClause)
|
|
}
|
|
|
|
handleGalleryPerformerTagsCriterion(&query, galleryFilter.PerformerTags)
|
|
|
|
query.sortAndPagination = qb.getGallerySort(findFilter) + getPagination(findFilter)
|
|
|
|
return query
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) Query(galleryFilter *models.GalleryFilterType, findFilter *models.FindFilterType) ([]*models.Gallery, int, error) {
|
|
query := qb.makeQuery(galleryFilter, findFilter)
|
|
|
|
idsResult, countResult, err := query.executeFind()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
var galleries []*models.Gallery
|
|
for _, id := range idsResult {
|
|
gallery, err := qb.Find(id)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
galleries = append(galleries, gallery)
|
|
}
|
|
|
|
return galleries, countResult, nil
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) QueryCount(galleryFilter *models.GalleryFilterType, findFilter *models.FindFilterType) (int, error) {
|
|
query := qb.makeQuery(galleryFilter, findFilter)
|
|
|
|
return query.executeCount()
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) handleAverageResolutionFilter(query *queryBuilder, resolutionFilter *models.ResolutionEnum) {
|
|
if resolutionFilter == nil {
|
|
return
|
|
}
|
|
|
|
if resolution := resolutionFilter.String(); resolutionFilter.IsValid() {
|
|
var low int
|
|
var high int
|
|
|
|
switch resolution {
|
|
case "VERY_LOW":
|
|
high = 240
|
|
case "LOW":
|
|
low = 240
|
|
high = 360
|
|
case "R360P":
|
|
low = 360
|
|
high = 480
|
|
case "STANDARD":
|
|
low = 480
|
|
high = 540
|
|
case "WEB_HD":
|
|
low = 540
|
|
high = 720
|
|
case "STANDARD_HD":
|
|
low = 720
|
|
high = 1080
|
|
case "FULL_HD":
|
|
low = 1080
|
|
high = 1440
|
|
case "QUAD_HD":
|
|
low = 1440
|
|
high = 1920
|
|
case "VR_HD":
|
|
low = 1920
|
|
high = 2160
|
|
case "FOUR_K":
|
|
low = 2160
|
|
high = 2880
|
|
case "FIVE_K":
|
|
low = 2880
|
|
high = 3384
|
|
case "SIX_K":
|
|
low = 3384
|
|
high = 4320
|
|
case "EIGHT_K":
|
|
low = 4320
|
|
}
|
|
|
|
havingClause := ""
|
|
if low != 0 {
|
|
havingClause = "avg(MIN(images.width, images.height)) >= " + strconv.Itoa(low)
|
|
}
|
|
if high != 0 {
|
|
if havingClause != "" {
|
|
havingClause += " AND "
|
|
}
|
|
havingClause += "avg(MIN(images.width, images.height)) < " + strconv.Itoa(high)
|
|
}
|
|
|
|
if havingClause != "" {
|
|
query.addHaving(havingClause)
|
|
}
|
|
}
|
|
}
|
|
|
|
func handleGalleryPerformerTagsCriterion(query *queryBuilder, performerTagsFilter *models.MultiCriterionInput) {
|
|
if performerTagsFilter != nil && len(performerTagsFilter.Value) > 0 {
|
|
for _, tagID := range performerTagsFilter.Value {
|
|
query.addArg(tagID)
|
|
}
|
|
|
|
query.body += " LEFT JOIN performers_tags AS performer_tags_join on performers_join.performer_id = performer_tags_join.performer_id"
|
|
|
|
if performerTagsFilter.Modifier == models.CriterionModifierIncludes {
|
|
// includes any of the provided ids
|
|
query.addWhere("performer_tags_join.tag_id IN " + getInBinding(len(performerTagsFilter.Value)))
|
|
} else if performerTagsFilter.Modifier == models.CriterionModifierIncludesAll {
|
|
// includes all of the provided ids
|
|
query.addWhere("performer_tags_join.tag_id IN " + getInBinding(len(performerTagsFilter.Value)))
|
|
query.addHaving(fmt.Sprintf("count(distinct performer_tags_join.tag_id) IS %d", len(performerTagsFilter.Value)))
|
|
} else if performerTagsFilter.Modifier == models.CriterionModifierExcludes {
|
|
query.addWhere(fmt.Sprintf(`not exists
|
|
(select performers_galleries.performer_id from performers_galleries
|
|
left join performers_tags on performers_tags.performer_id = performers_galleries.performer_id where
|
|
performers_galleries.gallery_id = galleries.id AND
|
|
performers_tags.tag_id in %s)`, getInBinding(len(performerTagsFilter.Value))))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) getGallerySort(findFilter *models.FindFilterType) string {
|
|
var sort string
|
|
var direction string
|
|
if findFilter == nil {
|
|
sort = "path"
|
|
direction = "ASC"
|
|
} else {
|
|
sort = findFilter.GetSort("path")
|
|
direction = findFilter.GetDirection()
|
|
}
|
|
|
|
switch sort {
|
|
case "tag_count":
|
|
return getCountSort(galleryTable, galleriesTagsTable, galleryIDColumn, direction)
|
|
case "performer_count":
|
|
return getCountSort(galleryTable, performersGalleriesTable, galleryIDColumn, direction)
|
|
default:
|
|
return getSort(sort, direction, "galleries")
|
|
}
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) queryGallery(query string, args []interface{}) (*models.Gallery, error) {
|
|
results, err := qb.queryGalleries(query, args)
|
|
if err != nil || len(results) < 1 {
|
|
return nil, err
|
|
}
|
|
return results[0], nil
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) queryGalleries(query string, args []interface{}) ([]*models.Gallery, error) {
|
|
var ret models.Galleries
|
|
if err := qb.query(query, args, &ret); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return []*models.Gallery(ret), nil
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) performersRepository() *joinRepository {
|
|
return &joinRepository{
|
|
repository: repository{
|
|
tx: qb.tx,
|
|
tableName: performersGalleriesTable,
|
|
idColumn: galleryIDColumn,
|
|
},
|
|
fkColumn: "performer_id",
|
|
}
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) GetPerformerIDs(galleryID int) ([]int, error) {
|
|
return qb.performersRepository().getIDs(galleryID)
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) UpdatePerformers(galleryID int, performerIDs []int) error {
|
|
// Delete the existing joins and then create new ones
|
|
return qb.performersRepository().replace(galleryID, performerIDs)
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) tagsRepository() *joinRepository {
|
|
return &joinRepository{
|
|
repository: repository{
|
|
tx: qb.tx,
|
|
tableName: galleriesTagsTable,
|
|
idColumn: galleryIDColumn,
|
|
},
|
|
fkColumn: "tag_id",
|
|
}
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) GetTagIDs(galleryID int) ([]int, error) {
|
|
return qb.tagsRepository().getIDs(galleryID)
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) UpdateTags(galleryID int, tagIDs []int) error {
|
|
// Delete the existing joins and then create new ones
|
|
return qb.tagsRepository().replace(galleryID, tagIDs)
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) imagesRepository() *joinRepository {
|
|
return &joinRepository{
|
|
repository: repository{
|
|
tx: qb.tx,
|
|
tableName: galleriesImagesTable,
|
|
idColumn: galleryIDColumn,
|
|
},
|
|
fkColumn: "image_id",
|
|
}
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) GetImageIDs(galleryID int) ([]int, error) {
|
|
return qb.imagesRepository().getIDs(galleryID)
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) UpdateImages(galleryID int, imageIDs []int) error {
|
|
// Delete the existing joins and then create new ones
|
|
return qb.imagesRepository().replace(galleryID, imageIDs)
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) scenesRepository() *joinRepository {
|
|
return &joinRepository{
|
|
repository: repository{
|
|
tx: qb.tx,
|
|
tableName: galleriesScenesTable,
|
|
idColumn: galleryIDColumn,
|
|
},
|
|
fkColumn: sceneIDColumn,
|
|
}
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) GetSceneIDs(galleryID int) ([]int, error) {
|
|
return qb.scenesRepository().getIDs(galleryID)
|
|
}
|
|
|
|
func (qb *galleryQueryBuilder) UpdateScenes(galleryID int, sceneIDs []int) error {
|
|
// Delete the existing joins and then create new ones
|
|
return qb.scenesRepository().replace(galleryID, sceneIDs)
|
|
}
|