mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 04:44:37 +03:00
Images section (#813)
* Add new configuration options * Refactor scan/clean * Schema changes * Add details to galleries * Remove redundant code * Refine thumbnail generation * Gallery overhaul * Don't allow modifying zip gallery images * Show gallery card overlays * Hide zoom slider when not in grid mode
This commit is contained in:
@@ -10,6 +10,7 @@ type GalleryReader interface {
|
||||
FindByChecksum(checksum string) (*Gallery, error)
|
||||
FindByPath(path string) (*Gallery, error)
|
||||
FindBySceneID(sceneID int) (*Gallery, error)
|
||||
FindByImageID(imageID int) ([]*Gallery, error)
|
||||
// ValidGalleriesForScenePath(scenePath string) ([]*Gallery, error)
|
||||
// Count() (int, error)
|
||||
All() ([]*Gallery, error)
|
||||
@@ -60,6 +61,10 @@ func (t *galleryReaderWriter) FindBySceneID(sceneID int) (*Gallery, error) {
|
||||
return t.qb.FindBySceneID(sceneID, t.tx)
|
||||
}
|
||||
|
||||
func (t *galleryReaderWriter) FindByImageID(imageID int) ([]*Gallery, error) {
|
||||
return t.qb.FindByImageID(imageID, t.tx)
|
||||
}
|
||||
|
||||
func (t *galleryReaderWriter) Create(newGallery Gallery) (*Gallery, error) {
|
||||
return t.qb.Create(newGallery, t.tx)
|
||||
}
|
||||
|
||||
72
pkg/models/image.go
Normal file
72
pkg/models/image.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
type ImageReader interface {
|
||||
// Find(id int) (*Image, error)
|
||||
FindMany(ids []int) ([]*Image, error)
|
||||
FindByChecksum(checksum string) (*Image, error)
|
||||
// FindByPath(path string) (*Image, error)
|
||||
// FindByPerformerID(performerID int) ([]*Image, error)
|
||||
// CountByPerformerID(performerID int) (int, error)
|
||||
// FindByStudioID(studioID int) ([]*Image, error)
|
||||
// Count() (int, error)
|
||||
// SizeCount() (string, error)
|
||||
// CountByStudioID(studioID int) (int, error)
|
||||
// CountByTagID(tagID int) (int, error)
|
||||
All() ([]*Image, error)
|
||||
// Query(imageFilter *ImageFilterType, findFilter *FindFilterType) ([]*Image, int)
|
||||
}
|
||||
|
||||
type ImageWriter interface {
|
||||
Create(newImage Image) (*Image, error)
|
||||
Update(updatedImage ImagePartial) (*Image, error)
|
||||
UpdateFull(updatedImage Image) (*Image, error)
|
||||
// IncrementOCounter(id int) (int, error)
|
||||
// DecrementOCounter(id int) (int, error)
|
||||
// ResetOCounter(id int) (int, error)
|
||||
// Destroy(id string) error
|
||||
}
|
||||
|
||||
type ImageReaderWriter interface {
|
||||
ImageReader
|
||||
ImageWriter
|
||||
}
|
||||
|
||||
func NewImageReaderWriter(tx *sqlx.Tx) ImageReaderWriter {
|
||||
return &imageReaderWriter{
|
||||
tx: tx,
|
||||
qb: NewImageQueryBuilder(),
|
||||
}
|
||||
}
|
||||
|
||||
type imageReaderWriter struct {
|
||||
tx *sqlx.Tx
|
||||
qb ImageQueryBuilder
|
||||
}
|
||||
|
||||
func (t *imageReaderWriter) FindMany(ids []int) ([]*Image, error) {
|
||||
return t.qb.FindMany(ids)
|
||||
}
|
||||
|
||||
func (t *imageReaderWriter) FindByChecksum(checksum string) (*Image, error) {
|
||||
return t.qb.FindByChecksum(checksum)
|
||||
}
|
||||
|
||||
func (t *imageReaderWriter) All() ([]*Image, error) {
|
||||
return t.qb.All()
|
||||
}
|
||||
|
||||
func (t *imageReaderWriter) Create(newImage Image) (*Image, error) {
|
||||
return t.qb.Create(newImage, t.tx)
|
||||
}
|
||||
|
||||
func (t *imageReaderWriter) Update(updatedImage ImagePartial) (*Image, error) {
|
||||
return t.qb.Update(updatedImage, t.tx)
|
||||
}
|
||||
|
||||
func (t *imageReaderWriter) UpdateFull(updatedImage Image) (*Image, error) {
|
||||
return t.qb.UpdateFull(updatedImage, t.tx)
|
||||
}
|
||||
@@ -28,6 +28,11 @@ type JoinWriter interface {
|
||||
// DestroySceneMarkersTags(sceneMarkerID int, updatedJoins []SceneMarkersTags) error
|
||||
// DestroyScenesGalleries(sceneID int) error
|
||||
// DestroyScenesMarkers(sceneID int) error
|
||||
UpdatePerformersGalleries(galleryID int, updatedJoins []PerformersGalleries) error
|
||||
UpdateGalleriesTags(galleryID int, updatedJoins []GalleriesTags) error
|
||||
UpdateGalleriesImages(imageID int, updatedJoins []GalleriesImages) error
|
||||
UpdatePerformersImages(imageID int, updatedJoins []PerformersImages) error
|
||||
UpdateImagesTags(imageID int, updatedJoins []ImagesTags) error
|
||||
}
|
||||
|
||||
type JoinReaderWriter interface {
|
||||
@@ -74,3 +79,23 @@ func (t *joinReaderWriter) UpdateScenesTags(sceneID int, updatedJoins []ScenesTa
|
||||
func (t *joinReaderWriter) UpdateSceneMarkersTags(sceneMarkerID int, updatedJoins []SceneMarkersTags) error {
|
||||
return t.qb.UpdateSceneMarkersTags(sceneMarkerID, updatedJoins, t.tx)
|
||||
}
|
||||
|
||||
func (t *joinReaderWriter) UpdatePerformersGalleries(galleryID int, updatedJoins []PerformersGalleries) error {
|
||||
return t.qb.UpdatePerformersGalleries(galleryID, updatedJoins, t.tx)
|
||||
}
|
||||
|
||||
func (t *joinReaderWriter) UpdateGalleriesTags(galleryID int, updatedJoins []GalleriesTags) error {
|
||||
return t.qb.UpdateGalleriesTags(galleryID, updatedJoins, t.tx)
|
||||
}
|
||||
|
||||
func (t *joinReaderWriter) UpdateGalleriesImages(imageID int, updatedJoins []GalleriesImages) error {
|
||||
return t.qb.UpdateGalleriesImages(imageID, updatedJoins, t.tx)
|
||||
}
|
||||
|
||||
func (t *joinReaderWriter) UpdatePerformersImages(imageID int, updatedJoins []PerformersImages) error {
|
||||
return t.qb.UpdatePerformersImages(imageID, updatedJoins, t.tx)
|
||||
}
|
||||
|
||||
func (t *joinReaderWriter) UpdateImagesTags(imageID int, updatedJoins []ImagesTags) error {
|
||||
return t.qb.UpdateImagesTags(imageID, updatedJoins, t.tx)
|
||||
}
|
||||
|
||||
@@ -81,6 +81,29 @@ func (_m *GalleryReaderWriter) FindByChecksum(checksum string) (*models.Gallery,
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// FindByImageID provides a mock function with given fields: imageID
|
||||
func (_m *GalleryReaderWriter) FindByImageID(imageID int) ([]*models.Gallery, error) {
|
||||
ret := _m.Called(imageID)
|
||||
|
||||
var r0 []*models.Gallery
|
||||
if rf, ok := ret.Get(0).(func(int) []*models.Gallery); ok {
|
||||
r0 = rf(imageID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*models.Gallery)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(int) error); ok {
|
||||
r1 = rf(imageID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// FindByPath provides a mock function with given fields: path
|
||||
func (_m *GalleryReaderWriter) FindByPath(path string) (*models.Gallery, error) {
|
||||
ret := _m.Called(path)
|
||||
|
||||
151
pkg/models/mocks/ImageReaderWriter.go
Normal file
151
pkg/models/mocks/ImageReaderWriter.go
Normal file
@@ -0,0 +1,151 @@
|
||||
// Code generated by mockery v0.0.0-dev. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
models "github.com/stashapp/stash/pkg/models"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// ImageReaderWriter is an autogenerated mock type for the ImageReaderWriter type
|
||||
type ImageReaderWriter struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// All provides a mock function with given fields:
|
||||
func (_m *ImageReaderWriter) All() ([]*models.Image, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 []*models.Image
|
||||
if rf, ok := ret.Get(0).(func() []*models.Image); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*models.Image)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Create provides a mock function with given fields: newImage
|
||||
func (_m *ImageReaderWriter) Create(newImage models.Image) (*models.Image, error) {
|
||||
ret := _m.Called(newImage)
|
||||
|
||||
var r0 *models.Image
|
||||
if rf, ok := ret.Get(0).(func(models.Image) *models.Image); ok {
|
||||
r0 = rf(newImage)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*models.Image)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(models.Image) error); ok {
|
||||
r1 = rf(newImage)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// FindByChecksum provides a mock function with given fields: checksum
|
||||
func (_m *ImageReaderWriter) FindByChecksum(checksum string) (*models.Image, error) {
|
||||
ret := _m.Called(checksum)
|
||||
|
||||
var r0 *models.Image
|
||||
if rf, ok := ret.Get(0).(func(string) *models.Image); ok {
|
||||
r0 = rf(checksum)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*models.Image)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(checksum)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// FindMany provides a mock function with given fields: ids
|
||||
func (_m *ImageReaderWriter) FindMany(ids []int) ([]*models.Image, error) {
|
||||
ret := _m.Called(ids)
|
||||
|
||||
var r0 []*models.Image
|
||||
if rf, ok := ret.Get(0).(func([]int) []*models.Image); ok {
|
||||
r0 = rf(ids)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*models.Image)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func([]int) error); ok {
|
||||
r1 = rf(ids)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Update provides a mock function with given fields: updatedImage
|
||||
func (_m *ImageReaderWriter) Update(updatedImage models.ImagePartial) (*models.Image, error) {
|
||||
ret := _m.Called(updatedImage)
|
||||
|
||||
var r0 *models.Image
|
||||
if rf, ok := ret.Get(0).(func(models.ImagePartial) *models.Image); ok {
|
||||
r0 = rf(updatedImage)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*models.Image)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(models.ImagePartial) error); ok {
|
||||
r1 = rf(updatedImage)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// UpdateFull provides a mock function with given fields: updatedImage
|
||||
func (_m *ImageReaderWriter) UpdateFull(updatedImage models.Image) (*models.Image, error) {
|
||||
ret := _m.Called(updatedImage)
|
||||
|
||||
var r0 *models.Image
|
||||
if rf, ok := ret.Get(0).(func(models.Image) *models.Image); ok {
|
||||
r0 = rf(updatedImage)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*models.Image)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(models.Image) error); ok {
|
||||
r1 = rf(updatedImage)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
@@ -63,6 +63,48 @@ func (_m *JoinReaderWriter) GetSceneMovies(sceneID int) ([]models.MoviesScenes,
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// UpdateGalleriesImages provides a mock function with given fields: imageID, updatedJoins
|
||||
func (_m *JoinReaderWriter) UpdateGalleriesImages(imageID int, updatedJoins []models.GalleriesImages) error {
|
||||
ret := _m.Called(imageID, updatedJoins)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(int, []models.GalleriesImages) error); ok {
|
||||
r0 = rf(imageID, updatedJoins)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// UpdateGalleriesTags provides a mock function with given fields: galleryID, updatedJoins
|
||||
func (_m *JoinReaderWriter) UpdateGalleriesTags(galleryID int, updatedJoins []models.GalleriesTags) error {
|
||||
ret := _m.Called(galleryID, updatedJoins)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(int, []models.GalleriesTags) error); ok {
|
||||
r0 = rf(galleryID, updatedJoins)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// UpdateImagesTags provides a mock function with given fields: imageID, updatedJoins
|
||||
func (_m *JoinReaderWriter) UpdateImagesTags(imageID int, updatedJoins []models.ImagesTags) error {
|
||||
ret := _m.Called(imageID, updatedJoins)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(int, []models.ImagesTags) error); ok {
|
||||
r0 = rf(imageID, updatedJoins)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// UpdateMoviesScenes provides a mock function with given fields: sceneID, updatedJoins
|
||||
func (_m *JoinReaderWriter) UpdateMoviesScenes(sceneID int, updatedJoins []models.MoviesScenes) error {
|
||||
ret := _m.Called(sceneID, updatedJoins)
|
||||
@@ -77,6 +119,34 @@ func (_m *JoinReaderWriter) UpdateMoviesScenes(sceneID int, updatedJoins []model
|
||||
return r0
|
||||
}
|
||||
|
||||
// UpdatePerformersGalleries provides a mock function with given fields: galleryID, updatedJoins
|
||||
func (_m *JoinReaderWriter) UpdatePerformersGalleries(galleryID int, updatedJoins []models.PerformersGalleries) error {
|
||||
ret := _m.Called(galleryID, updatedJoins)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(int, []models.PerformersGalleries) error); ok {
|
||||
r0 = rf(galleryID, updatedJoins)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// UpdatePerformersImages provides a mock function with given fields: imageID, updatedJoins
|
||||
func (_m *JoinReaderWriter) UpdatePerformersImages(imageID int, updatedJoins []models.PerformersImages) error {
|
||||
ret := _m.Called(imageID, updatedJoins)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(int, []models.PerformersImages) error); ok {
|
||||
r0 = rf(imageID, updatedJoins)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// UpdatePerformersScenes provides a mock function with given fields: sceneID, updatedJoins
|
||||
func (_m *JoinReaderWriter) UpdatePerformersScenes(sceneID int, updatedJoins []models.PerformersScenes) error {
|
||||
ret := _m.Called(sceneID, updatedJoins)
|
||||
|
||||
@@ -58,6 +58,52 @@ func (_m *PerformerReaderWriter) Create(newPerformer models.Performer) (*models.
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// FindByGalleryID provides a mock function with given fields: galleryID
|
||||
func (_m *PerformerReaderWriter) FindByGalleryID(galleryID int) ([]*models.Performer, error) {
|
||||
ret := _m.Called(galleryID)
|
||||
|
||||
var r0 []*models.Performer
|
||||
if rf, ok := ret.Get(0).(func(int) []*models.Performer); ok {
|
||||
r0 = rf(galleryID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*models.Performer)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(int) error); ok {
|
||||
r1 = rf(galleryID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// FindByImageID provides a mock function with given fields: imageID
|
||||
func (_m *PerformerReaderWriter) FindByImageID(imageID int) ([]*models.Performer, error) {
|
||||
ret := _m.Called(imageID)
|
||||
|
||||
var r0 []*models.Performer
|
||||
if rf, ok := ret.Get(0).(func(int) []*models.Performer); ok {
|
||||
r0 = rf(imageID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*models.Performer)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(int) error); ok {
|
||||
r1 = rf(imageID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// FindByNames provides a mock function with given fields: names, nocase
|
||||
func (_m *PerformerReaderWriter) FindByNames(names []string, nocase bool) ([]*models.Performer, error) {
|
||||
ret := _m.Called(names, nocase)
|
||||
|
||||
@@ -81,6 +81,52 @@ func (_m *TagReaderWriter) Find(id int) (*models.Tag, error) {
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// FindByGalleryID provides a mock function with given fields: galleryID
|
||||
func (_m *TagReaderWriter) FindByGalleryID(galleryID int) ([]*models.Tag, error) {
|
||||
ret := _m.Called(galleryID)
|
||||
|
||||
var r0 []*models.Tag
|
||||
if rf, ok := ret.Get(0).(func(int) []*models.Tag); ok {
|
||||
r0 = rf(galleryID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*models.Tag)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(int) error); ok {
|
||||
r1 = rf(galleryID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// FindByImageID provides a mock function with given fields: imageID
|
||||
func (_m *TagReaderWriter) FindByImageID(imageID int) ([]*models.Tag, error) {
|
||||
ret := _m.Called(imageID)
|
||||
|
||||
var r0 []*models.Tag
|
||||
if rf, ok := ret.Get(0).(func(int) []*models.Tag); ok {
|
||||
r0 = rf(imageID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*models.Tag)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(int) error); ok {
|
||||
r1 = rf(imageID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// FindByName provides a mock function with given fields: name, nocase
|
||||
func (_m *TagReaderWriter) FindByName(name string, nocase bool) (*models.Tag, error) {
|
||||
ret := _m.Called(name, nocase)
|
||||
|
||||
@@ -1,175 +1,40 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/stashapp/stash/pkg/api/urlbuilders"
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
_ "golang.org/x/image/webp"
|
||||
)
|
||||
|
||||
type Gallery struct {
|
||||
ID int `db:"id" json:"id"`
|
||||
Path string `db:"path" json:"path"`
|
||||
Path sql.NullString `db:"path" json:"path"`
|
||||
Checksum string `db:"checksum" json:"checksum"`
|
||||
Zip bool `db:"zip" json:"zip"`
|
||||
Title sql.NullString `db:"title" json:"title"`
|
||||
URL sql.NullString `db:"url" json:"url"`
|
||||
Date SQLiteDate `db:"date" json:"date"`
|
||||
Details sql.NullString `db:"details" json:"details"`
|
||||
Rating sql.NullInt64 `db:"rating" json:"rating"`
|
||||
StudioID sql.NullInt64 `db:"studio_id,omitempty" json:"studio_id"`
|
||||
SceneID sql.NullInt64 `db:"scene_id,omitempty" json:"scene_id"`
|
||||
CreatedAt SQLiteTimestamp `db:"created_at" json:"created_at"`
|
||||
UpdatedAt SQLiteTimestamp `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
const DefaultGthumbWidth int = 200
|
||||
|
||||
func (g *Gallery) CountFiles() int {
|
||||
filteredFiles, readCloser, err := g.listZipContents()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
defer readCloser.Close()
|
||||
|
||||
return len(filteredFiles)
|
||||
// GalleryPartial represents part of a Gallery object. It is used to update
|
||||
// the database entry. Only non-nil fields will be updated.
|
||||
type GalleryPartial struct {
|
||||
ID int `db:"id" json:"id"`
|
||||
Path *sql.NullString `db:"path" json:"path"`
|
||||
Checksum *string `db:"checksum" json:"checksum"`
|
||||
Title *sql.NullString `db:"title" json:"title"`
|
||||
URL *sql.NullString `db:"url" json:"url"`
|
||||
Date *SQLiteDate `db:"date" json:"date"`
|
||||
Details *sql.NullString `db:"details" json:"details"`
|
||||
Rating *sql.NullInt64 `db:"rating" json:"rating"`
|
||||
StudioID *sql.NullInt64 `db:"studio_id,omitempty" json:"studio_id"`
|
||||
SceneID *sql.NullInt64 `db:"scene_id,omitempty" json:"scene_id"`
|
||||
CreatedAt *SQLiteTimestamp `db:"created_at" json:"created_at"`
|
||||
UpdatedAt *SQLiteTimestamp `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (g *Gallery) GetFiles(baseURL string) []*GalleryFilesType {
|
||||
var galleryFiles []*GalleryFilesType
|
||||
filteredFiles, readCloser, err := g.listZipContents()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer readCloser.Close()
|
||||
|
||||
builder := urlbuilders.NewGalleryURLBuilder(baseURL, g.ID)
|
||||
for i, file := range filteredFiles {
|
||||
galleryURL := builder.GetGalleryImageURL(i)
|
||||
galleryFile := GalleryFilesType{
|
||||
Index: i,
|
||||
Name: &file.Name,
|
||||
Path: &galleryURL,
|
||||
}
|
||||
galleryFiles = append(galleryFiles, &galleryFile)
|
||||
}
|
||||
|
||||
return galleryFiles
|
||||
}
|
||||
|
||||
func (g *Gallery) GetImage(index int) []byte {
|
||||
data, _ := g.readZipFile(index)
|
||||
return data
|
||||
}
|
||||
|
||||
func (g *Gallery) GetThumbnail(index int, width int) []byte {
|
||||
data, _ := g.readZipFile(index)
|
||||
srcImage, _, err := image.Decode(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return data
|
||||
}
|
||||
resizedImage := imaging.Resize(srcImage, width, 0, imaging.Box)
|
||||
buf := new(bytes.Buffer)
|
||||
err = jpeg.Encode(buf, resizedImage, nil)
|
||||
if err != nil {
|
||||
return data
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (g *Gallery) readZipFile(index int) ([]byte, error) {
|
||||
filteredFiles, readCloser, err := g.listZipContents()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer readCloser.Close()
|
||||
|
||||
zipFile := filteredFiles[index]
|
||||
zipFileReadCloser, err := zipFile.Open()
|
||||
if err != nil {
|
||||
logger.Warn("failed to read file inside zip file")
|
||||
return nil, err
|
||||
}
|
||||
defer zipFileReadCloser.Close()
|
||||
|
||||
return ioutil.ReadAll(zipFileReadCloser)
|
||||
}
|
||||
|
||||
func (g *Gallery) listZipContents() ([]*zip.File, *zip.ReadCloser, error) {
|
||||
readCloser, err := zip.OpenReader(g.Path)
|
||||
if err != nil {
|
||||
logger.Warnf("failed to read zip file %s", g.Path)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
filteredFiles := make([]*zip.File, 0)
|
||||
for _, file := range readCloser.File {
|
||||
if file.FileInfo().IsDir() {
|
||||
continue
|
||||
}
|
||||
ext := filepath.Ext(file.Name)
|
||||
ext = strings.ToLower(ext)
|
||||
if ext != ".jpg" && ext != ".jpeg" && ext != ".png" && ext != ".gif" && ext != ".webp" {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(file.Name, "__MACOSX") {
|
||||
continue
|
||||
}
|
||||
filteredFiles = append(filteredFiles, file)
|
||||
}
|
||||
sort.Slice(filteredFiles, func(i, j int) bool {
|
||||
a := filteredFiles[i]
|
||||
b := filteredFiles[j]
|
||||
return utils.NaturalCompare(a.Name, b.Name)
|
||||
})
|
||||
|
||||
cover := contains(filteredFiles, "cover.jpg") // first image with cover.jpg in the name
|
||||
if cover >= 0 { // will be moved to the start
|
||||
reorderedFiles := reorder(filteredFiles, cover)
|
||||
if reorderedFiles != nil {
|
||||
return reorderedFiles, readCloser, nil
|
||||
}
|
||||
}
|
||||
|
||||
return filteredFiles, readCloser, nil
|
||||
}
|
||||
|
||||
// return index of first occurrenece of string x ( case insensitive ) in name of zip contents, -1 otherwise
|
||||
func contains(a []*zip.File, x string) int {
|
||||
for i, n := range a {
|
||||
if strings.Contains(strings.ToLower(n.Name), strings.ToLower(x)) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// reorder slice so that element with position toFirst gets at the start
|
||||
func reorder(a []*zip.File, toFirst int) []*zip.File {
|
||||
var first *zip.File
|
||||
switch {
|
||||
case toFirst < 0 || toFirst >= len(a):
|
||||
return nil
|
||||
case toFirst == 0:
|
||||
return a
|
||||
default:
|
||||
first = a[toFirst]
|
||||
copy(a[toFirst:], a[toFirst+1:]) // Shift a[toFirst+1:] left one index removing a[toFirst] element
|
||||
a[len(a)-1] = nil // Nil now unused element for garbage collection
|
||||
a = a[:len(a)-1] // Truncate slice
|
||||
a = append([]*zip.File{first}, a...) // Push first to the start of the slice
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (g *Gallery) ImageCount() int {
|
||||
images, _, _ := g.listZipContents()
|
||||
if images == nil {
|
||||
return 0
|
||||
}
|
||||
return len(images)
|
||||
}
|
||||
const DefaultGthumbWidth int = 640
|
||||
|
||||
44
pkg/models/model_image.go
Normal file
44
pkg/models/model_image.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
// Image stores the metadata for a single image.
|
||||
type Image struct {
|
||||
ID int `db:"id" json:"id"`
|
||||
Checksum string `db:"checksum" json:"checksum"`
|
||||
Path string `db:"path" json:"path"`
|
||||
Title sql.NullString `db:"title" json:"title"`
|
||||
Rating sql.NullInt64 `db:"rating" json:"rating"`
|
||||
OCounter int `db:"o_counter" json:"o_counter"`
|
||||
Size sql.NullInt64 `db:"size" json:"size"`
|
||||
Width sql.NullInt64 `db:"width" json:"width"`
|
||||
Height sql.NullInt64 `db:"height" json:"height"`
|
||||
StudioID sql.NullInt64 `db:"studio_id,omitempty" json:"studio_id"`
|
||||
CreatedAt SQLiteTimestamp `db:"created_at" json:"created_at"`
|
||||
UpdatedAt SQLiteTimestamp `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
// ImagePartial represents part of a Image object. It is used to update
|
||||
// the database entry. Only non-nil fields will be updated.
|
||||
type ImagePartial struct {
|
||||
ID int `db:"id" json:"id"`
|
||||
Checksum *string `db:"checksum" json:"checksum"`
|
||||
Path *string `db:"path" json:"path"`
|
||||
Title *sql.NullString `db:"title" json:"title"`
|
||||
Rating *sql.NullInt64 `db:"rating" json:"rating"`
|
||||
Size *sql.NullInt64 `db:"size" json:"size"`
|
||||
Width *sql.NullInt64 `db:"width" json:"width"`
|
||||
Height *sql.NullInt64 `db:"height" json:"height"`
|
||||
StudioID *sql.NullInt64 `db:"studio_id,omitempty" json:"studio_id"`
|
||||
CreatedAt *SQLiteTimestamp `db:"created_at" json:"created_at"`
|
||||
UpdatedAt *SQLiteTimestamp `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
// ImageFileType represents the file metadata for an image.
|
||||
type ImageFileType struct {
|
||||
Size *int `graphql:"size" json:"size"`
|
||||
Width *int `graphql:"width" json:"width"`
|
||||
Height *int `graphql:"height" json:"height"`
|
||||
}
|
||||
@@ -22,3 +22,28 @@ type SceneMarkersTags struct {
|
||||
SceneMarkerID int `db:"scene_marker_id" json:"scene_marker_id"`
|
||||
TagID int `db:"tag_id" json:"tag_id"`
|
||||
}
|
||||
|
||||
type PerformersImages struct {
|
||||
PerformerID int `db:"performer_id" json:"performer_id"`
|
||||
ImageID int `db:"image_id" json:"image_id"`
|
||||
}
|
||||
|
||||
type ImagesTags struct {
|
||||
ImageID int `db:"image_id" json:"image_id"`
|
||||
TagID int `db:"tag_id" json:"tag_id"`
|
||||
}
|
||||
|
||||
type GalleriesImages struct {
|
||||
GalleryID int `db:"gallery_id" json:"gallery_id"`
|
||||
ImageID int `db:"image_id" json:"image_id"`
|
||||
}
|
||||
|
||||
type PerformersGalleries struct {
|
||||
PerformerID int `db:"performer_id" json:"performer_id"`
|
||||
GalleryID int `db:"gallery_id" json:"gallery_id"`
|
||||
}
|
||||
|
||||
type GalleriesTags struct {
|
||||
TagID int `db:"tag_id" json:"tag_id"`
|
||||
GalleryID int `db:"gallery_id" json:"gallery_id"`
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ type PerformerReader interface {
|
||||
FindMany(ids []int) ([]*Performer, error)
|
||||
FindBySceneID(sceneID int) ([]*Performer, error)
|
||||
FindNamesBySceneID(sceneID int) ([]*Performer, error)
|
||||
FindByImageID(imageID int) ([]*Performer, error)
|
||||
FindByGalleryID(galleryID int) ([]*Performer, error)
|
||||
FindByNames(names []string, nocase bool) ([]*Performer, error)
|
||||
// Count() (int, error)
|
||||
All() ([]*Performer, error)
|
||||
@@ -66,6 +68,14 @@ func (t *performerReaderWriter) FindNamesBySceneID(sceneID int) ([]*Performer, e
|
||||
return t.qb.FindNameBySceneID(sceneID, t.tx)
|
||||
}
|
||||
|
||||
func (t *performerReaderWriter) FindByImageID(id int) ([]*Performer, error) {
|
||||
return t.qb.FindByImageID(id, t.tx)
|
||||
}
|
||||
|
||||
func (t *performerReaderWriter) FindByGalleryID(id int) ([]*Performer, error) {
|
||||
return t.qb.FindByGalleryID(id, t.tx)
|
||||
}
|
||||
|
||||
func (t *performerReaderWriter) Create(newPerformer Performer) (*Performer, error) {
|
||||
return t.qb.Create(newPerformer, t.tx)
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ func NewGalleryQueryBuilder() GalleryQueryBuilder {
|
||||
func (qb *GalleryQueryBuilder) Create(newGallery Gallery, tx *sqlx.Tx) (*Gallery, error) {
|
||||
ensureTx(tx)
|
||||
result, err := tx.NamedExec(
|
||||
`INSERT INTO galleries (path, checksum, scene_id, created_at, updated_at)
|
||||
VALUES (:path, :checksum, :scene_id, :created_at, :updated_at)
|
||||
`INSERT INTO galleries (path, checksum, zip, title, date, details, url, studio_id, rating, scene_id, created_at, updated_at)
|
||||
VALUES (:path, :checksum, :zip, :title, :date, :details, :url, :studio_id, :rating, :scene_id, :created_at, :updated_at)
|
||||
`,
|
||||
newGallery,
|
||||
)
|
||||
@@ -55,6 +55,19 @@ func (qb *GalleryQueryBuilder) Update(updatedGallery Gallery, tx *sqlx.Tx) (*Gal
|
||||
return &updatedGallery, nil
|
||||
}
|
||||
|
||||
func (qb *GalleryQueryBuilder) UpdatePartial(updatedGallery GalleryPartial, tx *sqlx.Tx) (*Gallery, error) {
|
||||
ensureTx(tx)
|
||||
_, err := tx.NamedExec(
|
||||
`UPDATE galleries SET `+SQLGenKeysPartial(updatedGallery)+` WHERE galleries.id = :id`,
|
||||
updatedGallery,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return qb.Find(updatedGallery.ID, tx)
|
||||
}
|
||||
|
||||
func (qb *GalleryQueryBuilder) Destroy(id int, tx *sqlx.Tx) error {
|
||||
return executeDeleteQuery("galleries", strconv.Itoa(id), tx)
|
||||
}
|
||||
@@ -77,16 +90,16 @@ func (qb *GalleryQueryBuilder) ClearGalleryId(sceneID int, tx *sqlx.Tx) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (qb *GalleryQueryBuilder) Find(id int) (*Gallery, error) {
|
||||
func (qb *GalleryQueryBuilder) Find(id int, tx *sqlx.Tx) (*Gallery, error) {
|
||||
query := "SELECT * FROM galleries WHERE id = ? LIMIT 1"
|
||||
args := []interface{}{id}
|
||||
return qb.queryGallery(query, args, nil)
|
||||
return qb.queryGallery(query, args, tx)
|
||||
}
|
||||
|
||||
func (qb *GalleryQueryBuilder) FindMany(ids []int) ([]*Gallery, error) {
|
||||
var galleries []*Gallery
|
||||
for _, id := range ids {
|
||||
gallery, err := qb.Find(id)
|
||||
gallery, err := qb.Find(id, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -125,6 +138,24 @@ func (qb *GalleryQueryBuilder) ValidGalleriesForScenePath(scenePath string) ([]*
|
||||
return qb.queryGalleries(query, nil, nil)
|
||||
}
|
||||
|
||||
func (qb *GalleryQueryBuilder) FindByImageID(imageID int, tx *sqlx.Tx) ([]*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, tx)
|
||||
}
|
||||
|
||||
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 runCountQuery(buildCountQuery(query), args)
|
||||
}
|
||||
|
||||
func (qb *GalleryQueryBuilder) Count() (int, error) {
|
||||
return runCountQuery(buildCountQuery("SELECT galleries.id FROM galleries"), nil)
|
||||
}
|
||||
@@ -146,6 +177,11 @@ func (qb *GalleryQueryBuilder) Query(galleryFilter *GalleryFilterType, findFilte
|
||||
}
|
||||
|
||||
query.body = selectDistinctIDs("galleries")
|
||||
query.body += `
|
||||
left join performers_galleries as performers_join on performers_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
|
||||
`
|
||||
|
||||
if q := findFilter.Q; q != nil && *q != "" {
|
||||
searchColumns := []string{"galleries.path", "galleries.checksum"}
|
||||
@@ -154,21 +190,73 @@ func (qb *GalleryQueryBuilder) Query(galleryFilter *GalleryFilterType, findFilte
|
||||
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")
|
||||
|
||||
if isMissingFilter := galleryFilter.IsMissing; isMissingFilter != nil && *isMissingFilter != "" {
|
||||
switch *isMissingFilter {
|
||||
case "scene":
|
||||
query.addWhere("galleries.scene_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", "tags_join", "gallery_id", "tag_id", tagsFilter)
|
||||
query.addWhere(whereClause)
|
||||
query.addHaving(havingClause)
|
||||
}
|
||||
|
||||
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_join", "gallery_id", "performer_id", performersFilter)
|
||||
query.addWhere(whereClause)
|
||||
query.addHaving(havingClause)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
query.sortAndPagination = qb.getGallerySort(findFilter) + getPagination(findFilter)
|
||||
idsResult, countResult := query.executeFind()
|
||||
|
||||
var galleries []*Gallery
|
||||
for _, id := range idsResult {
|
||||
gallery, _ := qb.Find(id)
|
||||
gallery, _ := qb.Find(id, nil)
|
||||
galleries = append(galleries, gallery)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,15 +14,15 @@ func TestGalleryFind(t *testing.T) {
|
||||
gqb := models.NewGalleryQueryBuilder()
|
||||
|
||||
const galleryIdx = 0
|
||||
gallery, err := gqb.Find(galleryIDs[galleryIdx])
|
||||
gallery, err := gqb.Find(galleryIDs[galleryIdx], nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error finding gallery: %s", err.Error())
|
||||
}
|
||||
|
||||
assert.Equal(t, getGalleryStringValue(galleryIdx, "Path"), gallery.Path)
|
||||
assert.Equal(t, getGalleryStringValue(galleryIdx, "Path"), gallery.Path.String)
|
||||
|
||||
gallery, err = gqb.Find(0)
|
||||
gallery, err = gqb.Find(0, nil)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error finding gallery: %s", err.Error())
|
||||
@@ -42,7 +42,7 @@ func TestGalleryFindByChecksum(t *testing.T) {
|
||||
t.Fatalf("Error finding gallery: %s", err.Error())
|
||||
}
|
||||
|
||||
assert.Equal(t, getGalleryStringValue(galleryIdx, "Path"), gallery.Path)
|
||||
assert.Equal(t, getGalleryStringValue(galleryIdx, "Path"), gallery.Path.String)
|
||||
|
||||
galleryChecksum = "not exist"
|
||||
gallery, err = gqb.FindByChecksum(galleryChecksum, nil)
|
||||
@@ -65,7 +65,7 @@ func TestGalleryFindByPath(t *testing.T) {
|
||||
t.Fatalf("Error finding gallery: %s", err.Error())
|
||||
}
|
||||
|
||||
assert.Equal(t, galleryPath, gallery.Path)
|
||||
assert.Equal(t, galleryPath, gallery.Path.String)
|
||||
|
||||
galleryPath = "not exist"
|
||||
gallery, err = gqb.FindByPath(galleryPath)
|
||||
@@ -87,7 +87,7 @@ func TestGalleryFindBySceneID(t *testing.T) {
|
||||
t.Fatalf("Error finding gallery: %s", err.Error())
|
||||
}
|
||||
|
||||
assert.Equal(t, getGalleryStringValue(galleryIdxWithScene, "Path"), gallery.Path)
|
||||
assert.Equal(t, getGalleryStringValue(galleryIdxWithScene, "Path"), gallery.Path.String)
|
||||
|
||||
gallery, err = gqb.FindBySceneID(0, nil)
|
||||
|
||||
@@ -149,7 +149,7 @@ func verifyGalleriesPath(t *testing.T, pathCriterion models.StringCriterionInput
|
||||
galleries, _ := sqb.Query(&galleryFilter, nil)
|
||||
|
||||
for _, gallery := range galleries {
|
||||
verifyString(t, gallery.Path, pathCriterion)
|
||||
verifyNullString(t, gallery.Path, pathCriterion)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
434
pkg/models/querybuilder_image.go
Normal file
434
pkg/models/querybuilder_image.go
Normal file
@@ -0,0 +1,434 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/stashapp/stash/pkg/database"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
)
|
||||
|
||||
const imageTable = "images"
|
||||
|
||||
var imagesForPerformerQuery = selectAll(imageTable) + `
|
||||
LEFT JOIN performers_images as performers_join on performers_join.image_id = images.id
|
||||
WHERE performers_join.performer_id = ?
|
||||
GROUP BY images.id
|
||||
`
|
||||
|
||||
var countImagesForPerformerQuery = `
|
||||
SELECT performer_id FROM performers_images as performers_join
|
||||
WHERE performer_id = ?
|
||||
GROUP BY image_id
|
||||
`
|
||||
|
||||
var imagesForStudioQuery = selectAll(imageTable) + `
|
||||
JOIN studios ON studios.id = images.studio_id
|
||||
WHERE studios.id = ?
|
||||
GROUP BY images.id
|
||||
`
|
||||
var imagesForMovieQuery = selectAll(imageTable) + `
|
||||
LEFT JOIN movies_images as movies_join on movies_join.image_id = images.id
|
||||
WHERE movies_join.movie_id = ?
|
||||
GROUP BY images.id
|
||||
`
|
||||
|
||||
var countImagesForTagQuery = `
|
||||
SELECT tag_id AS id FROM images_tags
|
||||
WHERE images_tags.tag_id = ?
|
||||
GROUP BY images_tags.image_id
|
||||
`
|
||||
|
||||
var imagesForGalleryQuery = selectAll(imageTable) + `
|
||||
LEFT JOIN galleries_images as galleries_join on galleries_join.image_id = images.id
|
||||
WHERE galleries_join.gallery_id = ?
|
||||
GROUP BY images.id
|
||||
`
|
||||
|
||||
var countImagesForGalleryQuery = `
|
||||
SELECT gallery_id FROM galleries_images
|
||||
WHERE gallery_id = ?
|
||||
GROUP BY image_id
|
||||
`
|
||||
|
||||
type ImageQueryBuilder struct{}
|
||||
|
||||
func NewImageQueryBuilder() ImageQueryBuilder {
|
||||
return ImageQueryBuilder{}
|
||||
}
|
||||
|
||||
func (qb *ImageQueryBuilder) Create(newImage Image, tx *sqlx.Tx) (*Image, error) {
|
||||
ensureTx(tx)
|
||||
result, err := tx.NamedExec(
|
||||
`INSERT INTO images (checksum, path, title, rating, o_counter, size,
|
||||
width, height, studio_id, created_at, updated_at)
|
||||
VALUES (:checksum, :path, :title, :rating, :o_counter, :size,
|
||||
:width, :height, :studio_id, :created_at, :updated_at)
|
||||
`,
|
||||
newImage,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imageID, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := tx.Get(&newImage, `SELECT * FROM images WHERE id = ? LIMIT 1`, imageID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &newImage, nil
|
||||
}
|
||||
|
||||
func (qb *ImageQueryBuilder) Update(updatedImage ImagePartial, tx *sqlx.Tx) (*Image, error) {
|
||||
ensureTx(tx)
|
||||
_, err := tx.NamedExec(
|
||||
`UPDATE images SET `+SQLGenKeysPartial(updatedImage)+` WHERE images.id = :id`,
|
||||
updatedImage,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return qb.find(updatedImage.ID, tx)
|
||||
}
|
||||
|
||||
func (qb *ImageQueryBuilder) UpdateFull(updatedImage Image, tx *sqlx.Tx) (*Image, error) {
|
||||
ensureTx(tx)
|
||||
_, err := tx.NamedExec(
|
||||
`UPDATE images SET `+SQLGenKeys(updatedImage)+` WHERE images.id = :id`,
|
||||
updatedImage,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return qb.find(updatedImage.ID, tx)
|
||||
}
|
||||
|
||||
func (qb *ImageQueryBuilder) IncrementOCounter(id int, tx *sqlx.Tx) (int, error) {
|
||||
ensureTx(tx)
|
||||
_, err := tx.Exec(
|
||||
`UPDATE images SET o_counter = o_counter + 1 WHERE images.id = ?`,
|
||||
id,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
image, err := qb.find(id, tx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return image.OCounter, nil
|
||||
}
|
||||
|
||||
func (qb *ImageQueryBuilder) DecrementOCounter(id int, tx *sqlx.Tx) (int, error) {
|
||||
ensureTx(tx)
|
||||
_, err := tx.Exec(
|
||||
`UPDATE images SET o_counter = o_counter - 1 WHERE images.id = ? and images.o_counter > 0`,
|
||||
id,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
image, err := qb.find(id, tx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return image.OCounter, nil
|
||||
}
|
||||
|
||||
func (qb *ImageQueryBuilder) ResetOCounter(id int, tx *sqlx.Tx) (int, error) {
|
||||
ensureTx(tx)
|
||||
_, err := tx.Exec(
|
||||
`UPDATE images SET o_counter = 0 WHERE images.id = ?`,
|
||||
id,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
image, err := qb.find(id, tx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return image.OCounter, nil
|
||||
}
|
||||
|
||||
func (qb *ImageQueryBuilder) Destroy(id int, tx *sqlx.Tx) error {
|
||||
return executeDeleteQuery("images", strconv.Itoa(id), tx)
|
||||
}
|
||||
func (qb *ImageQueryBuilder) Find(id int) (*Image, error) {
|
||||
return qb.find(id, nil)
|
||||
}
|
||||
|
||||
func (qb *ImageQueryBuilder) FindMany(ids []int) ([]*Image, error) {
|
||||
var images []*Image
|
||||
for _, id := range ids {
|
||||
image, err := qb.Find(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if image == nil {
|
||||
return nil, fmt.Errorf("image with id %d not found", id)
|
||||
}
|
||||
|
||||
images = append(images, image)
|
||||
}
|
||||
|
||||
return images, nil
|
||||
}
|
||||
|
||||
func (qb *ImageQueryBuilder) find(id int, tx *sqlx.Tx) (*Image, error) {
|
||||
query := selectAll(imageTable) + "WHERE id = ? LIMIT 1"
|
||||
args := []interface{}{id}
|
||||
return qb.queryImage(query, args, tx)
|
||||
}
|
||||
|
||||
func (qb *ImageQueryBuilder) FindByChecksum(checksum string) (*Image, error) {
|
||||
query := "SELECT * FROM images WHERE checksum = ? LIMIT 1"
|
||||
args := []interface{}{checksum}
|
||||
return qb.queryImage(query, args, nil)
|
||||
}
|
||||
|
||||
func (qb *ImageQueryBuilder) FindByPath(path string) (*Image, error) {
|
||||
query := selectAll(imageTable) + "WHERE path = ? LIMIT 1"
|
||||
args := []interface{}{path}
|
||||
return qb.queryImage(query, args, nil)
|
||||
}
|
||||
|
||||
func (qb *ImageQueryBuilder) FindByPerformerID(performerID int) ([]*Image, error) {
|
||||
args := []interface{}{performerID}
|
||||
return qb.queryImages(imagesForPerformerQuery, args, nil)
|
||||
}
|
||||
|
||||
func (qb *ImageQueryBuilder) CountByPerformerID(performerID int) (int, error) {
|
||||
args := []interface{}{performerID}
|
||||
return runCountQuery(buildCountQuery(countImagesForPerformerQuery), args)
|
||||
}
|
||||
|
||||
func (qb *ImageQueryBuilder) FindByStudioID(studioID int) ([]*Image, error) {
|
||||
args := []interface{}{studioID}
|
||||
return qb.queryImages(imagesForStudioQuery, args, nil)
|
||||
}
|
||||
|
||||
func (qb *ImageQueryBuilder) FindByGalleryID(galleryID int) ([]*Image, error) {
|
||||
args := []interface{}{galleryID}
|
||||
return qb.queryImages(imagesForGalleryQuery, args, nil)
|
||||
}
|
||||
|
||||
func (qb *ImageQueryBuilder) CountByGalleryID(galleryID int) (int, error) {
|
||||
args := []interface{}{galleryID}
|
||||
return runCountQuery(buildCountQuery(countImagesForGalleryQuery), args)
|
||||
}
|
||||
|
||||
func (qb *ImageQueryBuilder) Count() (int, error) {
|
||||
return runCountQuery(buildCountQuery("SELECT images.id FROM images"), nil)
|
||||
}
|
||||
|
||||
func (qb *ImageQueryBuilder) SizeCount() (string, error) {
|
||||
sum, err := runSumQuery("SELECT SUM(size) as sum FROM images", nil)
|
||||
if err != nil {
|
||||
return "0 B", err
|
||||
}
|
||||
return utils.HumanizeBytes(sum), err
|
||||
}
|
||||
|
||||
func (qb *ImageQueryBuilder) CountByStudioID(studioID int) (int, error) {
|
||||
args := []interface{}{studioID}
|
||||
return runCountQuery(buildCountQuery(imagesForStudioQuery), args)
|
||||
}
|
||||
|
||||
func (qb *ImageQueryBuilder) CountByTagID(tagID int) (int, error) {
|
||||
args := []interface{}{tagID}
|
||||
return runCountQuery(buildCountQuery(countImagesForTagQuery), args)
|
||||
}
|
||||
|
||||
func (qb *ImageQueryBuilder) All() ([]*Image, error) {
|
||||
return qb.queryImages(selectAll(imageTable)+qb.getImageSort(nil), nil, nil)
|
||||
}
|
||||
|
||||
func (qb *ImageQueryBuilder) Query(imageFilter *ImageFilterType, findFilter *FindFilterType) ([]*Image, int) {
|
||||
if imageFilter == nil {
|
||||
imageFilter = &ImageFilterType{}
|
||||
}
|
||||
if findFilter == nil {
|
||||
findFilter = &FindFilterType{}
|
||||
}
|
||||
|
||||
query := queryBuilder{
|
||||
tableName: imageTable,
|
||||
}
|
||||
|
||||
query.body = selectDistinctIDs(imageTable)
|
||||
query.body += `
|
||||
left join performers_images as performers_join on performers_join.image_id = images.id
|
||||
left join studios as studio on studio.id = images.studio_id
|
||||
left join images_tags as tags_join on tags_join.image_id = images.id
|
||||
left join galleries_images as galleries_join on galleries_join.image_id = images.id
|
||||
`
|
||||
|
||||
if q := findFilter.Q; q != nil && *q != "" {
|
||||
searchColumns := []string{"images.title", "images.path", "images.checksum"}
|
||||
clause, thisArgs := getSearchBinding(searchColumns, *q, false)
|
||||
query.addWhere(clause)
|
||||
query.addArg(thisArgs...)
|
||||
}
|
||||
|
||||
if rating := imageFilter.Rating; rating != nil {
|
||||
clause, count := getIntCriterionWhereClause("images.rating", *imageFilter.Rating)
|
||||
query.addWhere(clause)
|
||||
if count == 1 {
|
||||
query.addArg(imageFilter.Rating.Value)
|
||||
}
|
||||
}
|
||||
|
||||
if oCounter := imageFilter.OCounter; oCounter != nil {
|
||||
clause, count := getIntCriterionWhereClause("images.o_counter", *imageFilter.OCounter)
|
||||
query.addWhere(clause)
|
||||
if count == 1 {
|
||||
query.addArg(imageFilter.OCounter.Value)
|
||||
}
|
||||
}
|
||||
|
||||
if resolutionFilter := imageFilter.Resolution; resolutionFilter != nil {
|
||||
if resolution := resolutionFilter.String(); resolutionFilter.IsValid() {
|
||||
switch resolution {
|
||||
case "LOW":
|
||||
query.addWhere("images.height < 480")
|
||||
case "STANDARD":
|
||||
query.addWhere("(images.height >= 480 AND images.height < 720)")
|
||||
case "STANDARD_HD":
|
||||
query.addWhere("(images.height >= 720 AND images.height < 1080)")
|
||||
case "FULL_HD":
|
||||
query.addWhere("(images.height >= 1080 AND images.height < 2160)")
|
||||
case "FOUR_K":
|
||||
query.addWhere("images.height >= 2160")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isMissingFilter := imageFilter.IsMissing; isMissingFilter != nil && *isMissingFilter != "" {
|
||||
switch *isMissingFilter {
|
||||
case "studio":
|
||||
query.addWhere("images.studio_id IS NULL")
|
||||
case "performers":
|
||||
query.addWhere("performers_join.image_id IS NULL")
|
||||
case "galleries":
|
||||
query.addWhere("galleries_join.image_id IS NULL")
|
||||
case "tags":
|
||||
query.addWhere("tags_join.image_id IS NULL")
|
||||
default:
|
||||
query.addWhere("images." + *isMissingFilter + " IS NULL")
|
||||
}
|
||||
}
|
||||
|
||||
if tagsFilter := imageFilter.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("images", "tags", "images_tags", "image_id", "tag_id", tagsFilter)
|
||||
query.addWhere(whereClause)
|
||||
query.addHaving(havingClause)
|
||||
}
|
||||
|
||||
if galleriesFilter := imageFilter.Galleries; galleriesFilter != nil && len(galleriesFilter.Value) > 0 {
|
||||
for _, galleryID := range galleriesFilter.Value {
|
||||
query.addArg(galleryID)
|
||||
}
|
||||
|
||||
query.body += " LEFT JOIN galleries ON galleries_join.gallery_id = galleries.id"
|
||||
whereClause, havingClause := getMultiCriterionClause("images", "galleries", "galleries_images", "image_id", "gallery_id", galleriesFilter)
|
||||
query.addWhere(whereClause)
|
||||
query.addHaving(havingClause)
|
||||
}
|
||||
|
||||
if performersFilter := imageFilter.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("images", "performers", "performers_images", "image_id", "performer_id", performersFilter)
|
||||
query.addWhere(whereClause)
|
||||
query.addHaving(havingClause)
|
||||
}
|
||||
|
||||
if studiosFilter := imageFilter.Studios; studiosFilter != nil && len(studiosFilter.Value) > 0 {
|
||||
for _, studioID := range studiosFilter.Value {
|
||||
query.addArg(studioID)
|
||||
}
|
||||
|
||||
whereClause, havingClause := getMultiCriterionClause("images", "studio", "", "", "studio_id", studiosFilter)
|
||||
query.addWhere(whereClause)
|
||||
query.addHaving(havingClause)
|
||||
}
|
||||
|
||||
query.sortAndPagination = qb.getImageSort(findFilter) + getPagination(findFilter)
|
||||
idsResult, countResult := query.executeFind()
|
||||
|
||||
var images []*Image
|
||||
for _, id := range idsResult {
|
||||
image, _ := qb.Find(id)
|
||||
images = append(images, image)
|
||||
}
|
||||
|
||||
return images, countResult
|
||||
}
|
||||
|
||||
func (qb *ImageQueryBuilder) getImageSort(findFilter *FindFilterType) string {
|
||||
if findFilter == nil {
|
||||
return " ORDER BY images.path ASC "
|
||||
}
|
||||
sort := findFilter.GetSort("title")
|
||||
direction := findFilter.GetDirection()
|
||||
return getSort(sort, direction, "images")
|
||||
}
|
||||
|
||||
func (qb *ImageQueryBuilder) queryImage(query string, args []interface{}, tx *sqlx.Tx) (*Image, error) {
|
||||
results, err := qb.queryImages(query, args, tx)
|
||||
if err != nil || len(results) < 1 {
|
||||
return nil, err
|
||||
}
|
||||
return results[0], nil
|
||||
}
|
||||
|
||||
func (qb *ImageQueryBuilder) queryImages(query string, args []interface{}, tx *sqlx.Tx) ([]*Image, error) {
|
||||
var rows *sqlx.Rows
|
||||
var err error
|
||||
if tx != nil {
|
||||
rows, err = tx.Queryx(query, args...)
|
||||
} else {
|
||||
rows, err = database.DB.Queryx(query, args...)
|
||||
}
|
||||
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
images := make([]*Image, 0)
|
||||
for rows.Next() {
|
||||
image := Image{}
|
||||
if err := rows.StructScan(&image); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
images = append(images, &image)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return images, nil
|
||||
}
|
||||
624
pkg/models/querybuilder_image_test.go
Normal file
624
pkg/models/querybuilder_image_test.go
Normal file
@@ -0,0 +1,624 @@
|
||||
// +build integration
|
||||
|
||||
package models_test
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
)
|
||||
|
||||
func TestImageFind(t *testing.T) {
|
||||
// assume that the first image is imageWithGalleryPath
|
||||
sqb := models.NewImageQueryBuilder()
|
||||
|
||||
const imageIdx = 0
|
||||
imageID := imageIDs[imageIdx]
|
||||
image, err := sqb.Find(imageID)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error finding image: %s", err.Error())
|
||||
}
|
||||
|
||||
assert.Equal(t, getImageStringValue(imageIdx, "Path"), image.Path)
|
||||
|
||||
imageID = 0
|
||||
image, err = sqb.Find(imageID)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error finding image: %s", err.Error())
|
||||
}
|
||||
|
||||
assert.Nil(t, image)
|
||||
}
|
||||
|
||||
func TestImageFindByPath(t *testing.T) {
|
||||
sqb := models.NewImageQueryBuilder()
|
||||
|
||||
const imageIdx = 1
|
||||
imagePath := getImageStringValue(imageIdx, "Path")
|
||||
image, err := sqb.FindByPath(imagePath)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error finding image: %s", err.Error())
|
||||
}
|
||||
|
||||
assert.Equal(t, imageIDs[imageIdx], image.ID)
|
||||
assert.Equal(t, imagePath, image.Path)
|
||||
|
||||
imagePath = "not exist"
|
||||
image, err = sqb.FindByPath(imagePath)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error finding image: %s", err.Error())
|
||||
}
|
||||
|
||||
assert.Nil(t, image)
|
||||
}
|
||||
|
||||
func TestImageCountByPerformerID(t *testing.T) {
|
||||
sqb := models.NewImageQueryBuilder()
|
||||
count, err := sqb.CountByPerformerID(performerIDs[performerIdxWithImage])
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error counting images: %s", err.Error())
|
||||
}
|
||||
|
||||
assert.Equal(t, 1, count)
|
||||
|
||||
count, err = sqb.CountByPerformerID(0)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error counting images: %s", err.Error())
|
||||
}
|
||||
|
||||
assert.Equal(t, 0, count)
|
||||
}
|
||||
|
||||
func TestImageQueryQ(t *testing.T) {
|
||||
const imageIdx = 2
|
||||
|
||||
q := getImageStringValue(imageIdx, titleField)
|
||||
|
||||
sqb := models.NewImageQueryBuilder()
|
||||
|
||||
imageQueryQ(t, sqb, q, imageIdx)
|
||||
}
|
||||
|
||||
func imageQueryQ(t *testing.T, sqb models.ImageQueryBuilder, q string, expectedImageIdx int) {
|
||||
filter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
images, _ := sqb.Query(nil, &filter)
|
||||
|
||||
assert.Len(t, images, 1)
|
||||
image := images[0]
|
||||
assert.Equal(t, imageIDs[expectedImageIdx], image.ID)
|
||||
|
||||
// no Q should return all results
|
||||
filter.Q = nil
|
||||
images, _ = sqb.Query(nil, &filter)
|
||||
|
||||
assert.Len(t, images, totalImages)
|
||||
}
|
||||
|
||||
func TestImageQueryRating(t *testing.T) {
|
||||
const rating = 3
|
||||
ratingCriterion := models.IntCriterionInput{
|
||||
Value: rating,
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
}
|
||||
|
||||
verifyImagesRating(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierNotEquals
|
||||
verifyImagesRating(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||
verifyImagesRating(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierLessThan
|
||||
verifyImagesRating(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierIsNull
|
||||
verifyImagesRating(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierNotNull
|
||||
verifyImagesRating(t, ratingCriterion)
|
||||
}
|
||||
|
||||
func verifyImagesRating(t *testing.T, ratingCriterion models.IntCriterionInput) {
|
||||
sqb := models.NewImageQueryBuilder()
|
||||
imageFilter := models.ImageFilterType{
|
||||
Rating: &ratingCriterion,
|
||||
}
|
||||
|
||||
images, _ := sqb.Query(&imageFilter, nil)
|
||||
|
||||
for _, image := range images {
|
||||
verifyInt64(t, image.Rating, ratingCriterion)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageQueryOCounter(t *testing.T) {
|
||||
const oCounter = 1
|
||||
oCounterCriterion := models.IntCriterionInput{
|
||||
Value: oCounter,
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
}
|
||||
|
||||
verifyImagesOCounter(t, oCounterCriterion)
|
||||
|
||||
oCounterCriterion.Modifier = models.CriterionModifierNotEquals
|
||||
verifyImagesOCounter(t, oCounterCriterion)
|
||||
|
||||
oCounterCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||
verifyImagesOCounter(t, oCounterCriterion)
|
||||
|
||||
oCounterCriterion.Modifier = models.CriterionModifierLessThan
|
||||
verifyImagesOCounter(t, oCounterCriterion)
|
||||
}
|
||||
|
||||
func verifyImagesOCounter(t *testing.T, oCounterCriterion models.IntCriterionInput) {
|
||||
sqb := models.NewImageQueryBuilder()
|
||||
imageFilter := models.ImageFilterType{
|
||||
OCounter: &oCounterCriterion,
|
||||
}
|
||||
|
||||
images, _ := sqb.Query(&imageFilter, nil)
|
||||
|
||||
for _, image := range images {
|
||||
verifyInt(t, image.OCounter, oCounterCriterion)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageQueryResolution(t *testing.T) {
|
||||
verifyImagesResolution(t, models.ResolutionEnumLow)
|
||||
verifyImagesResolution(t, models.ResolutionEnumStandard)
|
||||
verifyImagesResolution(t, models.ResolutionEnumStandardHd)
|
||||
verifyImagesResolution(t, models.ResolutionEnumFullHd)
|
||||
verifyImagesResolution(t, models.ResolutionEnumFourK)
|
||||
verifyImagesResolution(t, models.ResolutionEnum("unknown"))
|
||||
}
|
||||
|
||||
func verifyImagesResolution(t *testing.T, resolution models.ResolutionEnum) {
|
||||
sqb := models.NewImageQueryBuilder()
|
||||
imageFilter := models.ImageFilterType{
|
||||
Resolution: &resolution,
|
||||
}
|
||||
|
||||
images, _ := sqb.Query(&imageFilter, nil)
|
||||
|
||||
for _, image := range images {
|
||||
verifyImageResolution(t, image.Height, resolution)
|
||||
}
|
||||
}
|
||||
|
||||
func verifyImageResolution(t *testing.T, height sql.NullInt64, resolution models.ResolutionEnum) {
|
||||
assert := assert.New(t)
|
||||
h := height.Int64
|
||||
|
||||
switch resolution {
|
||||
case models.ResolutionEnumLow:
|
||||
assert.True(h < 480)
|
||||
case models.ResolutionEnumStandard:
|
||||
assert.True(h >= 480 && h < 720)
|
||||
case models.ResolutionEnumStandardHd:
|
||||
assert.True(h >= 720 && h < 1080)
|
||||
case models.ResolutionEnumFullHd:
|
||||
assert.True(h >= 1080 && h < 2160)
|
||||
case models.ResolutionEnumFourK:
|
||||
assert.True(h >= 2160)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageQueryIsMissingGalleries(t *testing.T) {
|
||||
sqb := models.NewImageQueryBuilder()
|
||||
isMissing := "galleries"
|
||||
imageFilter := models.ImageFilterType{
|
||||
IsMissing: &isMissing,
|
||||
}
|
||||
|
||||
q := getImageStringValue(imageIdxWithGallery, titleField)
|
||||
findFilter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
|
||||
images, _ := sqb.Query(&imageFilter, &findFilter)
|
||||
|
||||
assert.Len(t, images, 0)
|
||||
|
||||
findFilter.Q = nil
|
||||
images, _ = sqb.Query(&imageFilter, &findFilter)
|
||||
|
||||
// ensure non of the ids equal the one with gallery
|
||||
for _, image := range images {
|
||||
assert.NotEqual(t, imageIDs[imageIdxWithGallery], image.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageQueryIsMissingStudio(t *testing.T) {
|
||||
sqb := models.NewImageQueryBuilder()
|
||||
isMissing := "studio"
|
||||
imageFilter := models.ImageFilterType{
|
||||
IsMissing: &isMissing,
|
||||
}
|
||||
|
||||
q := getImageStringValue(imageIdxWithStudio, titleField)
|
||||
findFilter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
|
||||
images, _ := sqb.Query(&imageFilter, &findFilter)
|
||||
|
||||
assert.Len(t, images, 0)
|
||||
|
||||
findFilter.Q = nil
|
||||
images, _ = sqb.Query(&imageFilter, &findFilter)
|
||||
|
||||
// ensure non of the ids equal the one with studio
|
||||
for _, image := range images {
|
||||
assert.NotEqual(t, imageIDs[imageIdxWithStudio], image.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageQueryIsMissingPerformers(t *testing.T) {
|
||||
sqb := models.NewImageQueryBuilder()
|
||||
isMissing := "performers"
|
||||
imageFilter := models.ImageFilterType{
|
||||
IsMissing: &isMissing,
|
||||
}
|
||||
|
||||
q := getImageStringValue(imageIdxWithPerformer, titleField)
|
||||
findFilter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
|
||||
images, _ := sqb.Query(&imageFilter, &findFilter)
|
||||
|
||||
assert.Len(t, images, 0)
|
||||
|
||||
findFilter.Q = nil
|
||||
images, _ = sqb.Query(&imageFilter, &findFilter)
|
||||
|
||||
assert.True(t, len(images) > 0)
|
||||
|
||||
// ensure non of the ids equal the one with movies
|
||||
for _, image := range images {
|
||||
assert.NotEqual(t, imageIDs[imageIdxWithPerformer], image.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageQueryIsMissingTags(t *testing.T) {
|
||||
sqb := models.NewImageQueryBuilder()
|
||||
isMissing := "tags"
|
||||
imageFilter := models.ImageFilterType{
|
||||
IsMissing: &isMissing,
|
||||
}
|
||||
|
||||
q := getImageStringValue(imageIdxWithTwoTags, titleField)
|
||||
findFilter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
|
||||
images, _ := sqb.Query(&imageFilter, &findFilter)
|
||||
|
||||
assert.Len(t, images, 0)
|
||||
|
||||
findFilter.Q = nil
|
||||
images, _ = sqb.Query(&imageFilter, &findFilter)
|
||||
|
||||
assert.True(t, len(images) > 0)
|
||||
}
|
||||
|
||||
func TestImageQueryIsMissingRating(t *testing.T) {
|
||||
sqb := models.NewImageQueryBuilder()
|
||||
isMissing := "rating"
|
||||
imageFilter := models.ImageFilterType{
|
||||
IsMissing: &isMissing,
|
||||
}
|
||||
|
||||
images, _ := sqb.Query(&imageFilter, nil)
|
||||
|
||||
assert.True(t, len(images) > 0)
|
||||
|
||||
// ensure date is null, empty or "0001-01-01"
|
||||
for _, image := range images {
|
||||
assert.True(t, !image.Rating.Valid)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageQueryPerformers(t *testing.T) {
|
||||
sqb := models.NewImageQueryBuilder()
|
||||
performerCriterion := models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(performerIDs[performerIdxWithImage]),
|
||||
strconv.Itoa(performerIDs[performerIdx1WithImage]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
}
|
||||
|
||||
imageFilter := models.ImageFilterType{
|
||||
Performers: &performerCriterion,
|
||||
}
|
||||
|
||||
images, _ := sqb.Query(&imageFilter, nil)
|
||||
|
||||
assert.Len(t, images, 2)
|
||||
|
||||
// ensure ids are correct
|
||||
for _, image := range images {
|
||||
assert.True(t, image.ID == imageIDs[imageIdxWithPerformer] || image.ID == imageIDs[imageIdxWithTwoPerformers])
|
||||
}
|
||||
|
||||
performerCriterion = models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(performerIDs[performerIdx1WithImage]),
|
||||
strconv.Itoa(performerIDs[performerIdx2WithImage]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludesAll,
|
||||
}
|
||||
|
||||
images, _ = sqb.Query(&imageFilter, nil)
|
||||
|
||||
assert.Len(t, images, 1)
|
||||
assert.Equal(t, imageIDs[imageIdxWithTwoPerformers], images[0].ID)
|
||||
|
||||
performerCriterion = models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(performerIDs[performerIdx1WithImage]),
|
||||
},
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
}
|
||||
|
||||
q := getImageStringValue(imageIdxWithTwoPerformers, titleField)
|
||||
findFilter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
|
||||
images, _ = sqb.Query(&imageFilter, &findFilter)
|
||||
assert.Len(t, images, 0)
|
||||
}
|
||||
|
||||
func TestImageQueryTags(t *testing.T) {
|
||||
sqb := models.NewImageQueryBuilder()
|
||||
tagCriterion := models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdxWithImage]),
|
||||
strconv.Itoa(tagIDs[tagIdx1WithImage]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
}
|
||||
|
||||
imageFilter := models.ImageFilterType{
|
||||
Tags: &tagCriterion,
|
||||
}
|
||||
|
||||
images, _ := sqb.Query(&imageFilter, nil)
|
||||
|
||||
assert.Len(t, images, 2)
|
||||
|
||||
// ensure ids are correct
|
||||
for _, image := range images {
|
||||
assert.True(t, image.ID == imageIDs[imageIdxWithTag] || image.ID == imageIDs[imageIdxWithTwoTags])
|
||||
}
|
||||
|
||||
tagCriterion = models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithImage]),
|
||||
strconv.Itoa(tagIDs[tagIdx2WithImage]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludesAll,
|
||||
}
|
||||
|
||||
images, _ = sqb.Query(&imageFilter, nil)
|
||||
|
||||
assert.Len(t, images, 1)
|
||||
assert.Equal(t, imageIDs[imageIdxWithTwoTags], images[0].ID)
|
||||
|
||||
tagCriterion = models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithImage]),
|
||||
},
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
}
|
||||
|
||||
q := getImageStringValue(imageIdxWithTwoTags, titleField)
|
||||
findFilter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
|
||||
images, _ = sqb.Query(&imageFilter, &findFilter)
|
||||
assert.Len(t, images, 0)
|
||||
}
|
||||
|
||||
func TestImageQueryStudio(t *testing.T) {
|
||||
sqb := models.NewImageQueryBuilder()
|
||||
studioCriterion := models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(studioIDs[studioIdxWithImage]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
}
|
||||
|
||||
imageFilter := models.ImageFilterType{
|
||||
Studios: &studioCriterion,
|
||||
}
|
||||
|
||||
images, _ := sqb.Query(&imageFilter, nil)
|
||||
|
||||
assert.Len(t, images, 1)
|
||||
|
||||
// ensure id is correct
|
||||
assert.Equal(t, imageIDs[imageIdxWithStudio], images[0].ID)
|
||||
|
||||
studioCriterion = models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(studioIDs[studioIdxWithImage]),
|
||||
},
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
}
|
||||
|
||||
q := getImageStringValue(imageIdxWithStudio, titleField)
|
||||
findFilter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
|
||||
images, _ = sqb.Query(&imageFilter, &findFilter)
|
||||
assert.Len(t, images, 0)
|
||||
}
|
||||
|
||||
func TestImageQuerySorting(t *testing.T) {
|
||||
sort := titleField
|
||||
direction := models.SortDirectionEnumAsc
|
||||
findFilter := models.FindFilterType{
|
||||
Sort: &sort,
|
||||
Direction: &direction,
|
||||
}
|
||||
|
||||
sqb := models.NewImageQueryBuilder()
|
||||
images, _ := sqb.Query(nil, &findFilter)
|
||||
|
||||
// images should be in same order as indexes
|
||||
firstImage := images[0]
|
||||
lastImage := images[len(images)-1]
|
||||
|
||||
assert.Equal(t, imageIDs[0], firstImage.ID)
|
||||
assert.Equal(t, imageIDs[len(imageIDs)-1], lastImage.ID)
|
||||
|
||||
// sort in descending order
|
||||
direction = models.SortDirectionEnumDesc
|
||||
|
||||
images, _ = sqb.Query(nil, &findFilter)
|
||||
firstImage = images[0]
|
||||
lastImage = images[len(images)-1]
|
||||
|
||||
assert.Equal(t, imageIDs[len(imageIDs)-1], firstImage.ID)
|
||||
assert.Equal(t, imageIDs[0], lastImage.ID)
|
||||
}
|
||||
|
||||
func TestImageQueryPagination(t *testing.T) {
|
||||
perPage := 1
|
||||
findFilter := models.FindFilterType{
|
||||
PerPage: &perPage,
|
||||
}
|
||||
|
||||
sqb := models.NewImageQueryBuilder()
|
||||
images, _ := sqb.Query(nil, &findFilter)
|
||||
|
||||
assert.Len(t, images, 1)
|
||||
|
||||
firstID := images[0].ID
|
||||
|
||||
page := 2
|
||||
findFilter.Page = &page
|
||||
images, _ = sqb.Query(nil, &findFilter)
|
||||
|
||||
assert.Len(t, images, 1)
|
||||
secondID := images[0].ID
|
||||
assert.NotEqual(t, firstID, secondID)
|
||||
|
||||
perPage = 2
|
||||
page = 1
|
||||
|
||||
images, _ = sqb.Query(nil, &findFilter)
|
||||
assert.Len(t, images, 2)
|
||||
assert.Equal(t, firstID, images[0].ID)
|
||||
assert.Equal(t, secondID, images[1].ID)
|
||||
}
|
||||
|
||||
func TestImageCountByTagID(t *testing.T) {
|
||||
sqb := models.NewImageQueryBuilder()
|
||||
|
||||
imageCount, err := sqb.CountByTagID(tagIDs[tagIdxWithImage])
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("error calling CountByTagID: %s", err.Error())
|
||||
}
|
||||
|
||||
assert.Equal(t, 1, imageCount)
|
||||
|
||||
imageCount, err = sqb.CountByTagID(0)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("error calling CountByTagID: %s", err.Error())
|
||||
}
|
||||
|
||||
assert.Equal(t, 0, imageCount)
|
||||
}
|
||||
|
||||
func TestImageCountByStudioID(t *testing.T) {
|
||||
sqb := models.NewImageQueryBuilder()
|
||||
|
||||
imageCount, err := sqb.CountByStudioID(studioIDs[studioIdxWithImage])
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("error calling CountByStudioID: %s", err.Error())
|
||||
}
|
||||
|
||||
assert.Equal(t, 1, imageCount)
|
||||
|
||||
imageCount, err = sqb.CountByStudioID(0)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("error calling CountByStudioID: %s", err.Error())
|
||||
}
|
||||
|
||||
assert.Equal(t, 0, imageCount)
|
||||
}
|
||||
|
||||
func TestImageFindByPerformerID(t *testing.T) {
|
||||
sqb := models.NewImageQueryBuilder()
|
||||
|
||||
images, err := sqb.FindByPerformerID(performerIDs[performerIdxWithImage])
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("error calling FindByPerformerID: %s", err.Error())
|
||||
}
|
||||
|
||||
assert.Len(t, images, 1)
|
||||
assert.Equal(t, imageIDs[imageIdxWithPerformer], images[0].ID)
|
||||
|
||||
images, err = sqb.FindByPerformerID(0)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("error calling FindByPerformerID: %s", err.Error())
|
||||
}
|
||||
|
||||
assert.Len(t, images, 0)
|
||||
}
|
||||
|
||||
func TestImageFindByStudioID(t *testing.T) {
|
||||
sqb := models.NewImageQueryBuilder()
|
||||
|
||||
images, err := sqb.FindByStudioID(performerIDs[studioIdxWithImage])
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("error calling FindByStudioID: %s", err.Error())
|
||||
}
|
||||
|
||||
assert.Len(t, images, 1)
|
||||
assert.Equal(t, imageIDs[imageIdxWithStudio], images[0].ID)
|
||||
|
||||
images, err = sqb.FindByStudioID(0)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("error calling FindByStudioID: %s", err.Error())
|
||||
}
|
||||
|
||||
assert.Len(t, images, 0)
|
||||
}
|
||||
|
||||
// TODO Update
|
||||
// TODO IncrementOCounter
|
||||
// TODO DecrementOCounter
|
||||
// TODO ResetOCounter
|
||||
// TODO Destroy
|
||||
// TODO FindByChecksum
|
||||
// TODO Count
|
||||
// TODO SizeCount
|
||||
// TODO All
|
||||
@@ -365,3 +365,523 @@ func (qb *JoinsQueryBuilder) DestroyScenesMarkers(sceneID int, tx *sqlx.Tx) erro
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (qb *JoinsQueryBuilder) GetImagePerformers(imageID int, tx *sqlx.Tx) ([]PerformersImages, error) {
|
||||
ensureTx(tx)
|
||||
|
||||
// Delete the existing joins and then create new ones
|
||||
query := `SELECT * from performers_images WHERE image_id = ?`
|
||||
|
||||
var rows *sqlx.Rows
|
||||
var err error
|
||||
if tx != nil {
|
||||
rows, err = tx.Queryx(query, imageID)
|
||||
} else {
|
||||
rows, err = database.DB.Queryx(query, imageID)
|
||||
}
|
||||
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
performerImages := make([]PerformersImages, 0)
|
||||
for rows.Next() {
|
||||
performerImage := PerformersImages{}
|
||||
if err := rows.StructScan(&performerImage); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
performerImages = append(performerImages, performerImage)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return performerImages, nil
|
||||
}
|
||||
|
||||
func (qb *JoinsQueryBuilder) CreatePerformersImages(newJoins []PerformersImages, tx *sqlx.Tx) error {
|
||||
ensureTx(tx)
|
||||
for _, join := range newJoins {
|
||||
_, err := tx.NamedExec(
|
||||
`INSERT INTO performers_images (performer_id, image_id) VALUES (:performer_id, :image_id)`,
|
||||
join,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddPerformerImage adds a performer to a image. It does not make any change
|
||||
// if the performer already exists on the image. It returns true if image
|
||||
// performer was added.
|
||||
func (qb *JoinsQueryBuilder) AddPerformerImage(imageID int, performerID int, tx *sqlx.Tx) (bool, error) {
|
||||
ensureTx(tx)
|
||||
|
||||
existingPerformers, err := qb.GetImagePerformers(imageID, tx)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// ensure not already present
|
||||
for _, p := range existingPerformers {
|
||||
if p.PerformerID == performerID && p.ImageID == imageID {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
performerJoin := PerformersImages{
|
||||
PerformerID: performerID,
|
||||
ImageID: imageID,
|
||||
}
|
||||
performerJoins := append(existingPerformers, performerJoin)
|
||||
|
||||
err = qb.UpdatePerformersImages(imageID, performerJoins, tx)
|
||||
|
||||
return err == nil, err
|
||||
}
|
||||
|
||||
func (qb *JoinsQueryBuilder) UpdatePerformersImages(imageID int, updatedJoins []PerformersImages, tx *sqlx.Tx) error {
|
||||
ensureTx(tx)
|
||||
|
||||
// Delete the existing joins and then create new ones
|
||||
_, err := tx.Exec("DELETE FROM performers_images WHERE image_id = ?", imageID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return qb.CreatePerformersImages(updatedJoins, tx)
|
||||
}
|
||||
|
||||
func (qb *JoinsQueryBuilder) DestroyPerformersImages(imageID int, tx *sqlx.Tx) error {
|
||||
ensureTx(tx)
|
||||
|
||||
// Delete the existing joins
|
||||
_, err := tx.Exec("DELETE FROM performers_images WHERE image_id = ?", imageID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (qb *JoinsQueryBuilder) GetImageTags(imageID int, tx *sqlx.Tx) ([]ImagesTags, error) {
|
||||
ensureTx(tx)
|
||||
|
||||
// Delete the existing joins and then create new ones
|
||||
query := `SELECT * from images_tags WHERE image_id = ?`
|
||||
|
||||
var rows *sqlx.Rows
|
||||
var err error
|
||||
if tx != nil {
|
||||
rows, err = tx.Queryx(query, imageID)
|
||||
} else {
|
||||
rows, err = database.DB.Queryx(query, imageID)
|
||||
}
|
||||
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
imageTags := make([]ImagesTags, 0)
|
||||
for rows.Next() {
|
||||
imageTag := ImagesTags{}
|
||||
if err := rows.StructScan(&imageTag); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imageTags = append(imageTags, imageTag)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return imageTags, nil
|
||||
}
|
||||
|
||||
func (qb *JoinsQueryBuilder) CreateImagesTags(newJoins []ImagesTags, tx *sqlx.Tx) error {
|
||||
ensureTx(tx)
|
||||
for _, join := range newJoins {
|
||||
_, err := tx.NamedExec(
|
||||
`INSERT INTO images_tags (image_id, tag_id) VALUES (:image_id, :tag_id)`,
|
||||
join,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qb *JoinsQueryBuilder) UpdateImagesTags(imageID int, updatedJoins []ImagesTags, tx *sqlx.Tx) error {
|
||||
ensureTx(tx)
|
||||
|
||||
// Delete the existing joins and then create new ones
|
||||
_, err := tx.Exec("DELETE FROM images_tags WHERE image_id = ?", imageID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return qb.CreateImagesTags(updatedJoins, tx)
|
||||
}
|
||||
|
||||
// AddImageTag adds a tag to a image. It does not make any change if the tag
|
||||
// already exists on the image. It returns true if image tag was added.
|
||||
func (qb *JoinsQueryBuilder) AddImageTag(imageID int, tagID int, tx *sqlx.Tx) (bool, error) {
|
||||
ensureTx(tx)
|
||||
|
||||
existingTags, err := qb.GetImageTags(imageID, tx)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// ensure not already present
|
||||
for _, p := range existingTags {
|
||||
if p.TagID == tagID && p.ImageID == imageID {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
tagJoin := ImagesTags{
|
||||
TagID: tagID,
|
||||
ImageID: imageID,
|
||||
}
|
||||
tagJoins := append(existingTags, tagJoin)
|
||||
|
||||
err = qb.UpdateImagesTags(imageID, tagJoins, tx)
|
||||
|
||||
return err == nil, err
|
||||
}
|
||||
|
||||
func (qb *JoinsQueryBuilder) DestroyImagesTags(imageID int, tx *sqlx.Tx) error {
|
||||
ensureTx(tx)
|
||||
|
||||
// Delete the existing joins
|
||||
_, err := tx.Exec("DELETE FROM images_tags WHERE image_id = ?", imageID)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (qb *JoinsQueryBuilder) GetImageGalleries(imageID int, tx *sqlx.Tx) ([]GalleriesImages, error) {
|
||||
ensureTx(tx)
|
||||
|
||||
// Delete the existing joins and then create new ones
|
||||
query := `SELECT * from galleries_images WHERE image_id = ?`
|
||||
|
||||
var rows *sqlx.Rows
|
||||
var err error
|
||||
if tx != nil {
|
||||
rows, err = tx.Queryx(query, imageID)
|
||||
} else {
|
||||
rows, err = database.DB.Queryx(query, imageID)
|
||||
}
|
||||
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
galleryImages := make([]GalleriesImages, 0)
|
||||
for rows.Next() {
|
||||
galleriesImages := GalleriesImages{}
|
||||
if err := rows.StructScan(&galleriesImages); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
galleryImages = append(galleryImages, galleriesImages)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return galleryImages, nil
|
||||
}
|
||||
|
||||
func (qb *JoinsQueryBuilder) CreateGalleriesImages(newJoins []GalleriesImages, tx *sqlx.Tx) error {
|
||||
ensureTx(tx)
|
||||
for _, join := range newJoins {
|
||||
_, err := tx.NamedExec(
|
||||
`INSERT INTO galleries_images (gallery_id, image_id) VALUES (:gallery_id, :image_id)`,
|
||||
join,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qb *JoinsQueryBuilder) UpdateGalleriesImages(imageID int, updatedJoins []GalleriesImages, tx *sqlx.Tx) error {
|
||||
ensureTx(tx)
|
||||
|
||||
// Delete the existing joins and then create new ones
|
||||
_, err := tx.Exec("DELETE FROM galleries_images WHERE image_id = ?", imageID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return qb.CreateGalleriesImages(updatedJoins, tx)
|
||||
}
|
||||
|
||||
// AddGalleryImage adds a gallery to an image. It does not make any change if the tag
|
||||
// already exists on the image. It returns true if image tag was added.
|
||||
func (qb *JoinsQueryBuilder) AddImageGallery(imageID int, galleryID int, tx *sqlx.Tx) (bool, error) {
|
||||
ensureTx(tx)
|
||||
|
||||
existingGalleries, err := qb.GetImageGalleries(imageID, tx)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// ensure not already present
|
||||
for _, p := range existingGalleries {
|
||||
if p.GalleryID == galleryID && p.ImageID == imageID {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
galleryJoin := GalleriesImages{
|
||||
GalleryID: galleryID,
|
||||
ImageID: imageID,
|
||||
}
|
||||
galleryJoins := append(existingGalleries, galleryJoin)
|
||||
|
||||
err = qb.UpdateGalleriesImages(imageID, galleryJoins, tx)
|
||||
|
||||
return err == nil, err
|
||||
}
|
||||
|
||||
// RemoveImageGallery removes a gallery from an image. Returns true if the join
|
||||
// was removed.
|
||||
func (qb *JoinsQueryBuilder) RemoveImageGallery(imageID int, galleryID int, tx *sqlx.Tx) (bool, error) {
|
||||
ensureTx(tx)
|
||||
|
||||
existingGalleries, err := qb.GetImageGalleries(imageID, tx)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// remove the join
|
||||
var updatedJoins []GalleriesImages
|
||||
found := false
|
||||
for _, p := range existingGalleries {
|
||||
if p.GalleryID == galleryID && p.ImageID == imageID {
|
||||
found = true
|
||||
continue
|
||||
}
|
||||
|
||||
updatedJoins = append(updatedJoins, p)
|
||||
}
|
||||
|
||||
if found {
|
||||
err = qb.UpdateGalleriesImages(imageID, updatedJoins, tx)
|
||||
}
|
||||
|
||||
return found && err == nil, err
|
||||
}
|
||||
|
||||
func (qb *JoinsQueryBuilder) DestroyImageGalleries(imageID int, tx *sqlx.Tx) error {
|
||||
ensureTx(tx)
|
||||
|
||||
// Delete the existing joins
|
||||
_, err := tx.Exec("DELETE FROM galleries_images WHERE image_id = ?", imageID)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (qb *JoinsQueryBuilder) GetGalleryPerformers(galleryID int, tx *sqlx.Tx) ([]PerformersGalleries, error) {
|
||||
ensureTx(tx)
|
||||
|
||||
// Delete the existing joins and then create new ones
|
||||
query := `SELECT * from performers_galleries WHERE gallery_id = ?`
|
||||
|
||||
var rows *sqlx.Rows
|
||||
var err error
|
||||
if tx != nil {
|
||||
rows, err = tx.Queryx(query, galleryID)
|
||||
} else {
|
||||
rows, err = database.DB.Queryx(query, galleryID)
|
||||
}
|
||||
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
performerGalleries := make([]PerformersGalleries, 0)
|
||||
for rows.Next() {
|
||||
performerGallery := PerformersGalleries{}
|
||||
if err := rows.StructScan(&performerGallery); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
performerGalleries = append(performerGalleries, performerGallery)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return performerGalleries, nil
|
||||
}
|
||||
|
||||
func (qb *JoinsQueryBuilder) CreatePerformersGalleries(newJoins []PerformersGalleries, tx *sqlx.Tx) error {
|
||||
ensureTx(tx)
|
||||
for _, join := range newJoins {
|
||||
_, err := tx.NamedExec(
|
||||
`INSERT INTO performers_galleries (performer_id, gallery_id) VALUES (:performer_id, :gallery_id)`,
|
||||
join,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddPerformerGallery adds a performer to a gallery. It does not make any change
|
||||
// if the performer already exists on the gallery. It returns true if gallery
|
||||
// performer was added.
|
||||
func (qb *JoinsQueryBuilder) AddPerformerGallery(galleryID int, performerID int, tx *sqlx.Tx) (bool, error) {
|
||||
ensureTx(tx)
|
||||
|
||||
existingPerformers, err := qb.GetGalleryPerformers(galleryID, tx)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// ensure not already present
|
||||
for _, p := range existingPerformers {
|
||||
if p.PerformerID == performerID && p.GalleryID == galleryID {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
performerJoin := PerformersGalleries{
|
||||
PerformerID: performerID,
|
||||
GalleryID: galleryID,
|
||||
}
|
||||
performerJoins := append(existingPerformers, performerJoin)
|
||||
|
||||
err = qb.UpdatePerformersGalleries(galleryID, performerJoins, tx)
|
||||
|
||||
return err == nil, err
|
||||
}
|
||||
|
||||
func (qb *JoinsQueryBuilder) UpdatePerformersGalleries(galleryID int, updatedJoins []PerformersGalleries, tx *sqlx.Tx) error {
|
||||
ensureTx(tx)
|
||||
|
||||
// Delete the existing joins and then create new ones
|
||||
_, err := tx.Exec("DELETE FROM performers_galleries WHERE gallery_id = ?", galleryID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return qb.CreatePerformersGalleries(updatedJoins, tx)
|
||||
}
|
||||
|
||||
func (qb *JoinsQueryBuilder) DestroyPerformersGalleries(galleryID int, tx *sqlx.Tx) error {
|
||||
ensureTx(tx)
|
||||
|
||||
// Delete the existing joins
|
||||
_, err := tx.Exec("DELETE FROM performers_galleries WHERE gallery_id = ?", galleryID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (qb *JoinsQueryBuilder) GetGalleryTags(galleryID int, tx *sqlx.Tx) ([]GalleriesTags, error) {
|
||||
ensureTx(tx)
|
||||
|
||||
// Delete the existing joins and then create new ones
|
||||
query := `SELECT * from galleries_tags WHERE gallery_id = ?`
|
||||
|
||||
var rows *sqlx.Rows
|
||||
var err error
|
||||
if tx != nil {
|
||||
rows, err = tx.Queryx(query, galleryID)
|
||||
} else {
|
||||
rows, err = database.DB.Queryx(query, galleryID)
|
||||
}
|
||||
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
galleryTags := make([]GalleriesTags, 0)
|
||||
for rows.Next() {
|
||||
galleryTag := GalleriesTags{}
|
||||
if err := rows.StructScan(&galleryTag); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
galleryTags = append(galleryTags, galleryTag)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return galleryTags, nil
|
||||
}
|
||||
|
||||
func (qb *JoinsQueryBuilder) CreateGalleriesTags(newJoins []GalleriesTags, tx *sqlx.Tx) error {
|
||||
ensureTx(tx)
|
||||
for _, join := range newJoins {
|
||||
_, err := tx.NamedExec(
|
||||
`INSERT INTO galleries_tags (gallery_id, tag_id) VALUES (:gallery_id, :tag_id)`,
|
||||
join,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qb *JoinsQueryBuilder) UpdateGalleriesTags(galleryID int, updatedJoins []GalleriesTags, tx *sqlx.Tx) error {
|
||||
ensureTx(tx)
|
||||
|
||||
// Delete the existing joins and then create new ones
|
||||
_, err := tx.Exec("DELETE FROM galleries_tags WHERE gallery_id = ?", galleryID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return qb.CreateGalleriesTags(updatedJoins, tx)
|
||||
}
|
||||
|
||||
// AddGalleryTag adds a tag to a gallery. It does not make any change if the tag
|
||||
// already exists on the gallery. It returns true if gallery tag was added.
|
||||
func (qb *JoinsQueryBuilder) AddGalleryTag(galleryID int, tagID int, tx *sqlx.Tx) (bool, error) {
|
||||
ensureTx(tx)
|
||||
|
||||
existingTags, err := qb.GetGalleryTags(galleryID, tx)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// ensure not already present
|
||||
for _, p := range existingTags {
|
||||
if p.TagID == tagID && p.GalleryID == galleryID {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
tagJoin := GalleriesTags{
|
||||
TagID: tagID,
|
||||
GalleryID: galleryID,
|
||||
}
|
||||
tagJoins := append(existingTags, tagJoin)
|
||||
|
||||
err = qb.UpdateGalleriesTags(galleryID, tagJoins, tx)
|
||||
|
||||
return err == nil, err
|
||||
}
|
||||
|
||||
func (qb *JoinsQueryBuilder) DestroyGalleriesTags(galleryID int, tx *sqlx.Tx) error {
|
||||
ensureTx(tx)
|
||||
|
||||
// Delete the existing joins
|
||||
_, err := tx.Exec("DELETE FROM galleries_tags WHERE gallery_id = ?", galleryID)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -104,6 +104,24 @@ func (qb *PerformerQueryBuilder) FindBySceneID(sceneID int, tx *sqlx.Tx) ([]*Per
|
||||
return qb.queryPerformers(query, args, tx)
|
||||
}
|
||||
|
||||
func (qb *PerformerQueryBuilder) FindByImageID(imageID int, tx *sqlx.Tx) ([]*Performer, error) {
|
||||
query := selectAll("performers") + `
|
||||
LEFT JOIN performers_images as images_join on images_join.performer_id = performers.id
|
||||
WHERE images_join.image_id = ?
|
||||
`
|
||||
args := []interface{}{imageID}
|
||||
return qb.queryPerformers(query, args, tx)
|
||||
}
|
||||
|
||||
func (qb *PerformerQueryBuilder) FindByGalleryID(galleryID int, tx *sqlx.Tx) ([]*Performer, error) {
|
||||
query := selectAll("performers") + `
|
||||
LEFT JOIN performers_galleries as galleries_join on galleries_join.performer_id = performers.id
|
||||
WHERE galleries_join.gallery_id = ?
|
||||
`
|
||||
args := []interface{}{galleryID}
|
||||
return qb.queryPerformers(query, args, tx)
|
||||
}
|
||||
|
||||
func (qb *PerformerQueryBuilder) FindNameBySceneID(sceneID int, tx *sqlx.Tx) ([]*Performer, error) {
|
||||
query := `
|
||||
SELECT performers.name FROM performers
|
||||
|
||||
@@ -418,6 +418,8 @@ func sqlGenKeys(i interface{}, partial bool) string {
|
||||
if partial || t != 0 {
|
||||
query = append(query, fmt.Sprintf("%s=:%s", key, key))
|
||||
}
|
||||
case bool:
|
||||
query = append(query, fmt.Sprintf("%s=:%s", key, key))
|
||||
case SQLiteTimestamp:
|
||||
if partial || !t.Timestamp.IsZero() {
|
||||
query = append(query, fmt.Sprintf("%s=:%s", key, key))
|
||||
|
||||
@@ -120,6 +120,30 @@ func (qb *TagQueryBuilder) FindBySceneID(sceneID int, tx *sqlx.Tx) ([]*Tag, erro
|
||||
return qb.queryTags(query, args, tx)
|
||||
}
|
||||
|
||||
func (qb *TagQueryBuilder) FindByImageID(imageID int, tx *sqlx.Tx) ([]*Tag, error) {
|
||||
query := `
|
||||
SELECT tags.* FROM tags
|
||||
LEFT JOIN images_tags as images_join on images_join.tag_id = tags.id
|
||||
WHERE images_join.image_id = ?
|
||||
GROUP BY tags.id
|
||||
`
|
||||
query += qb.getTagSort(nil)
|
||||
args := []interface{}{imageID}
|
||||
return qb.queryTags(query, args, tx)
|
||||
}
|
||||
|
||||
func (qb *TagQueryBuilder) FindByGalleryID(galleryID int, tx *sqlx.Tx) ([]*Tag, error) {
|
||||
query := `
|
||||
SELECT tags.* FROM tags
|
||||
LEFT JOIN galleries_tags as galleries_join on galleries_join.tag_id = tags.id
|
||||
WHERE galleries_join.gallery_id = ?
|
||||
GROUP BY tags.id
|
||||
`
|
||||
query += qb.getTagSort(nil)
|
||||
args := []interface{}{galleryID}
|
||||
return qb.queryTags(query, args, tx)
|
||||
}
|
||||
|
||||
func (qb *TagQueryBuilder) FindBySceneMarkerID(sceneMarkerID int, tx *sqlx.Tx) ([]*Tag, error) {
|
||||
query := `
|
||||
SELECT tags.* FROM tags
|
||||
|
||||
@@ -116,7 +116,7 @@ func TestTagQueryIsMissingImage(t *testing.T) {
|
||||
IsMissing: &isMissing,
|
||||
}
|
||||
|
||||
q := getTagStringValue(tagIdxWithImage, "name")
|
||||
q := getTagStringValue(tagIdxWithCoverImage, "name")
|
||||
findFilter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
@@ -130,7 +130,7 @@ func TestTagQueryIsMissingImage(t *testing.T) {
|
||||
|
||||
// ensure non of the ids equal the one with image
|
||||
for _, tag := range tags {
|
||||
assert.NotEqual(t, tagIDs[tagIdxWithImage], tag.ID)
|
||||
assert.NotEqual(t, tagIDs[tagIdxWithCoverImage], tag.ID)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,21 +17,24 @@ import (
|
||||
|
||||
"github.com/stashapp/stash/pkg/database"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/models/modelstest"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
)
|
||||
|
||||
const totalScenes = 12
|
||||
const performersNameCase = 3
|
||||
const totalImages = 6
|
||||
const performersNameCase = 6
|
||||
const performersNameNoCase = 2
|
||||
const moviesNameCase = 2
|
||||
const moviesNameNoCase = 1
|
||||
const totalGalleries = 2
|
||||
const totalGalleries = 3
|
||||
const tagsNameNoCase = 2
|
||||
const tagsNameCase = 6
|
||||
const studiosNameCase = 4
|
||||
const tagsNameCase = 9
|
||||
const studiosNameCase = 5
|
||||
const studiosNameNoCase = 1
|
||||
|
||||
var sceneIDs []int
|
||||
var imageIDs []int
|
||||
var performerIDs []int
|
||||
var movieIDs []int
|
||||
var galleryIDs []int
|
||||
@@ -53,13 +56,23 @@ const sceneIdxWithTwoTags = 5
|
||||
const sceneIdxWithStudio = 6
|
||||
const sceneIdxWithMarker = 7
|
||||
|
||||
const imageIdxWithGallery = 0
|
||||
const imageIdxWithPerformer = 1
|
||||
const imageIdxWithTwoPerformers = 2
|
||||
const imageIdxWithTag = 3
|
||||
const imageIdxWithTwoTags = 4
|
||||
const imageIdxWithStudio = 5
|
||||
|
||||
const performerIdxWithScene = 0
|
||||
const performerIdx1WithScene = 1
|
||||
const performerIdx2WithScene = 2
|
||||
const performerIdxWithImage = 3
|
||||
const performerIdx1WithImage = 4
|
||||
const performerIdx2WithImage = 5
|
||||
|
||||
// performers with dup names start from the end
|
||||
const performerIdx1WithDupName = 3
|
||||
const performerIdxWithDupName = 4
|
||||
const performerIdx1WithDupName = 6
|
||||
const performerIdxWithDupName = 7
|
||||
|
||||
const movieIdxWithScene = 0
|
||||
const movieIdxWithStudio = 1
|
||||
@@ -68,25 +81,30 @@ const movieIdxWithStudio = 1
|
||||
const movieIdxWithDupName = 2
|
||||
|
||||
const galleryIdxWithScene = 0
|
||||
const galleryIdxWithImage = 1
|
||||
|
||||
const tagIdxWithScene = 0
|
||||
const tagIdx1WithScene = 1
|
||||
const tagIdx2WithScene = 2
|
||||
const tagIdxWithPrimaryMarker = 3
|
||||
const tagIdxWithMarker = 4
|
||||
const tagIdxWithImage = 5
|
||||
const tagIdxWithCoverImage = 5
|
||||
const tagIdxWithImage = 6
|
||||
const tagIdx1WithImage = 7
|
||||
const tagIdx2WithImage = 8
|
||||
|
||||
// tags with dup names start from the end
|
||||
const tagIdx1WithDupName = 6
|
||||
const tagIdxWithDupName = 7
|
||||
const tagIdx1WithDupName = 9
|
||||
const tagIdxWithDupName = 10
|
||||
|
||||
const studioIdxWithScene = 0
|
||||
const studioIdxWithMovie = 1
|
||||
const studioIdxWithChildStudio = 2
|
||||
const studioIdxWithParentStudio = 3
|
||||
const studioIdxWithImage = 4
|
||||
|
||||
// studios with dup names start from the end
|
||||
const studioIdxWithDupName = 4
|
||||
const studioIdxWithDupName = 5
|
||||
|
||||
const markerIdxWithScene = 0
|
||||
|
||||
@@ -144,6 +162,11 @@ func populateDB() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := createImages(tx, totalImages); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
if err := createGalleries(tx, totalGalleries); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
@@ -164,7 +187,7 @@ func populateDB() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := addTagImage(tx, tagIdxWithImage); err != nil {
|
||||
if err := addTagImage(tx, tagIdxWithCoverImage); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
@@ -207,6 +230,26 @@ func populateDB() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := linkImageGallery(tx, imageIdxWithGallery, galleryIdxWithImage); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
if err := linkImagePerformers(tx); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
if err := linkImageTags(tx); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
if err := linkImageStudio(tx, imageIdxWithStudio, studioIdxWithImage); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
if err := linkMovieStudio(tx, movieIdxWithStudio, studioIdxWithMovie); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
@@ -233,12 +276,12 @@ func getSceneStringValue(index int, field string) string {
|
||||
return fmt.Sprintf("scene_%04d_%s", index, field)
|
||||
}
|
||||
|
||||
func getSceneRating(index int) sql.NullInt64 {
|
||||
func getRating(index int) sql.NullInt64 {
|
||||
rating := index % 6
|
||||
return sql.NullInt64{Int64: int64(rating), Valid: rating > 0}
|
||||
}
|
||||
|
||||
func getSceneOCounter(index int) int {
|
||||
func getOCounter(index int) int {
|
||||
return index % 3
|
||||
}
|
||||
|
||||
@@ -252,7 +295,7 @@ func getSceneDuration(index int) sql.NullFloat64 {
|
||||
}
|
||||
}
|
||||
|
||||
func getSceneHeight(index int) sql.NullInt64 {
|
||||
func getHeight(index int) sql.NullInt64 {
|
||||
heights := []int64{0, 200, 240, 300, 480, 700, 720, 800, 1080, 1500, 2160, 3000}
|
||||
height := heights[index%len(heights)]
|
||||
return sql.NullInt64{
|
||||
@@ -279,10 +322,10 @@ func createScenes(tx *sqlx.Tx, n int) error {
|
||||
Title: sql.NullString{String: getSceneStringValue(i, titleField), Valid: true},
|
||||
Checksum: sql.NullString{String: getSceneStringValue(i, checksumField), Valid: true},
|
||||
Details: sql.NullString{String: getSceneStringValue(i, "Details"), Valid: true},
|
||||
Rating: getSceneRating(i),
|
||||
OCounter: getSceneOCounter(i),
|
||||
Rating: getRating(i),
|
||||
OCounter: getOCounter(i),
|
||||
Duration: getSceneDuration(i),
|
||||
Height: getSceneHeight(i),
|
||||
Height: getHeight(i),
|
||||
Date: getSceneDate(i),
|
||||
}
|
||||
|
||||
@@ -298,6 +341,35 @@ func createScenes(tx *sqlx.Tx, n int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getImageStringValue(index int, field string) string {
|
||||
return fmt.Sprintf("image_%04d_%s", index, field)
|
||||
}
|
||||
|
||||
func createImages(tx *sqlx.Tx, n int) error {
|
||||
qb := models.NewImageQueryBuilder()
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
image := models.Image{
|
||||
Path: getImageStringValue(i, pathField),
|
||||
Title: sql.NullString{String: getImageStringValue(i, titleField), Valid: true},
|
||||
Checksum: getImageStringValue(i, checksumField),
|
||||
Rating: getRating(i),
|
||||
OCounter: getOCounter(i),
|
||||
Height: getHeight(i),
|
||||
}
|
||||
|
||||
created, err := qb.Create(image, tx)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating image %v+: %s", image, err.Error())
|
||||
}
|
||||
|
||||
imageIDs = append(imageIDs, created.ID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getGalleryStringValue(index int, field string) string {
|
||||
return "gallery_" + strconv.FormatInt(int64(index), 10) + "_" + field
|
||||
}
|
||||
@@ -307,7 +379,7 @@ func createGalleries(tx *sqlx.Tx, n int) error {
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
gallery := models.Gallery{
|
||||
Path: getGalleryStringValue(i, pathField),
|
||||
Path: modelstest.NullString(getGalleryStringValue(i, pathField)),
|
||||
Checksum: getGalleryStringValue(i, checksumField),
|
||||
}
|
||||
|
||||
@@ -591,7 +663,7 @@ func linkScenePerformer(tx *sqlx.Tx, sceneIndex, performerIndex int) error {
|
||||
func linkSceneGallery(tx *sqlx.Tx, sceneIndex, galleryIndex int) error {
|
||||
gqb := models.NewGalleryQueryBuilder()
|
||||
|
||||
gallery, err := gqb.Find(galleryIDs[galleryIndex])
|
||||
gallery, err := gqb.Find(galleryIDs[galleryIndex], nil)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error finding gallery: %s", err.Error())
|
||||
@@ -640,6 +712,68 @@ func linkSceneStudio(tx *sqlx.Tx, sceneIndex, studioIndex int) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func linkImageGallery(tx *sqlx.Tx, imageIndex, galleryIndex int) error {
|
||||
jqb := models.NewJoinsQueryBuilder()
|
||||
|
||||
_, err := jqb.AddImageGallery(imageIDs[imageIndex], galleryIDs[galleryIndex], tx)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func linkImageTags(tx *sqlx.Tx) error {
|
||||
if err := linkImageTag(tx, imageIdxWithTag, tagIdxWithImage); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := linkImageTag(tx, imageIdxWithTwoTags, tagIdx1WithImage); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := linkImageTag(tx, imageIdxWithTwoTags, tagIdx2WithImage); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func linkImageTag(tx *sqlx.Tx, imageIndex, tagIndex int) error {
|
||||
jqb := models.NewJoinsQueryBuilder()
|
||||
|
||||
_, err := jqb.AddImageTag(imageIDs[imageIndex], tagIDs[tagIndex], tx)
|
||||
return err
|
||||
}
|
||||
|
||||
func linkImageStudio(tx *sqlx.Tx, imageIndex, studioIndex int) error {
|
||||
sqb := models.NewImageQueryBuilder()
|
||||
|
||||
image := models.ImagePartial{
|
||||
ID: imageIDs[imageIndex],
|
||||
StudioID: &sql.NullInt64{Int64: int64(studioIDs[studioIndex]), Valid: true},
|
||||
}
|
||||
_, err := sqb.Update(image, tx)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func linkImagePerformers(tx *sqlx.Tx) error {
|
||||
if err := linkImagePerformer(tx, imageIdxWithPerformer, performerIdxWithImage); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := linkImagePerformer(tx, imageIdxWithTwoPerformers, performerIdx1WithImage); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := linkImagePerformer(tx, imageIdxWithTwoPerformers, performerIdx2WithImage); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func linkImagePerformer(tx *sqlx.Tx, imageIndex, performerIndex int) error {
|
||||
jqb := models.NewJoinsQueryBuilder()
|
||||
|
||||
_, err := jqb.AddPerformerImage(imageIDs[imageIndex], performerIDs[performerIndex], tx)
|
||||
return err
|
||||
}
|
||||
|
||||
func linkMovieStudio(tx *sqlx.Tx, movieIndex, studioIndex int) error {
|
||||
mqb := models.NewMovieQueryBuilder()
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ type TagReader interface {
|
||||
FindMany(ids []int) ([]*Tag, error)
|
||||
FindBySceneID(sceneID int) ([]*Tag, error)
|
||||
FindBySceneMarkerID(sceneMarkerID int) ([]*Tag, error)
|
||||
FindByImageID(imageID int) ([]*Tag, error)
|
||||
FindByGalleryID(galleryID int) ([]*Tag, error)
|
||||
FindByName(name string, nocase bool) (*Tag, error)
|
||||
FindByNames(names []string, nocase bool) ([]*Tag, error)
|
||||
// Count() (int, error)
|
||||
@@ -75,6 +77,14 @@ func (t *tagReaderWriter) FindBySceneID(sceneID int) ([]*Tag, error) {
|
||||
return t.qb.FindBySceneID(sceneID, t.tx)
|
||||
}
|
||||
|
||||
func (t *tagReaderWriter) FindByImageID(imageID int) ([]*Tag, error) {
|
||||
return t.qb.FindByImageID(imageID, t.tx)
|
||||
}
|
||||
|
||||
func (t *tagReaderWriter) FindByGalleryID(imageID int) ([]*Tag, error) {
|
||||
return t.qb.FindByGalleryID(imageID, t.tx)
|
||||
}
|
||||
|
||||
func (t *tagReaderWriter) Create(newTag Tag) (*Tag, error) {
|
||||
return t.qb.Create(newTag, t.tx)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user