File storage rewrite (#2676)

* Restructure data layer part 2 (#2599)
* Refactor and separate image model
* Refactor image query builder
* Handle relationships in image query builder
* Remove relationship management methods
* Refactor gallery model/query builder
* Add scenes to gallery model
* Convert scene model
* Refactor scene models
* Remove unused methods
* Add unit tests for gallery
* Add image tests
* Add scene tests
* Convert unnecessary scene value pointers to values
* Convert unnecessary pointer values to values
* Refactor scene partial
* Add scene partial tests
* Refactor ImagePartial
* Add image partial tests
* Refactor gallery partial update
* Add partial gallery update tests
* Use zero/null package for null values
* Add files and scan system
* Add sqlite implementation for files/folders
* Add unit tests for files/folders
* Image refactors
* Update image data layer
* Refactor gallery model and creation
* Refactor scene model
* Refactor scenes
* Don't set title from filename
* Allow galleries to freely add/remove images
* Add multiple scene file support to graphql and UI
* Add multiple file support for images in graphql/UI
* Add multiple file for galleries in graphql/UI
* Remove use of some deprecated fields
* Remove scene path usage
* Remove gallery path usage
* Remove path from image
* Move funscript to video file
* Refactor caption detection
* Migrate existing data
* Add post commit/rollback hook system
* Lint. Comment out import/export tests
* Add WithDatabase read only wrapper
* Prepend tasks to list
* Add 32 pre-migration
* Add warnings in release and migration notes
This commit is contained in:
WithoutPants
2022-07-13 16:30:54 +10:00
parent 30877c75fb
commit 5495d72849
359 changed files with 43690 additions and 16000 deletions

19
pkg/models/date.go Normal file
View File

@@ -0,0 +1,19 @@
package models
import "time"
// Date wraps a time.Time with a format of "YYYY-MM-DD"
type Date struct {
time.Time
}
const dateFormat = "2006-01-02"
func (d Date) String() string {
return d.Format(dateFormat)
}
func NewDate(s string) Date {
t, _ := time.Parse(dateFormat, s)
return Date{t}
}

80
pkg/models/file.go Normal file
View File

@@ -0,0 +1,80 @@
package models
import (
"context"
"path/filepath"
"strings"
"github.com/stashapp/stash/pkg/file"
)
type FileQueryOptions struct {
QueryOptions
FileFilter *FileFilterType
}
type FileFilterType struct {
And *FileFilterType `json:"AND"`
Or *FileFilterType `json:"OR"`
Not *FileFilterType `json:"NOT"`
// Filter by path
Path *StringCriterionInput `json:"path"`
}
func PathsFileFilter(paths []string) *FileFilterType {
if paths == nil {
return nil
}
sep := string(filepath.Separator)
var ret *FileFilterType
var or *FileFilterType
for _, p := range paths {
newOr := &FileFilterType{}
if or != nil {
or.Or = newOr
} else {
ret = newOr
}
or = newOr
if !strings.HasSuffix(p, sep) {
p += sep
}
or.Path = &StringCriterionInput{
Modifier: CriterionModifierEquals,
Value: p + "%",
}
}
return ret
}
type FileQueryResult struct {
// can't use QueryResult because id type is wrong
IDs []file.ID
Count int
finder file.Finder
files []file.File
resolveErr error
}
func NewFileQueryResult(finder file.Finder) *FileQueryResult {
return &FileQueryResult{
finder: finder,
}
}
func (r *FileQueryResult) Resolve(ctx context.Context) ([]file.File, error) {
// cache results
if r.files == nil && r.resolveErr == nil {
r.files, r.resolveErr = r.finder.Find(ctx, r.IDs...)
}
return r.files, r.resolveErr
}

View File

@@ -1,6 +1,10 @@
package models
import "context"
import (
"context"
"github.com/stashapp/stash/pkg/file"
)
type GalleryFilterType struct {
And *GalleryFilterType `json:"AND"`
@@ -71,30 +75,23 @@ type GalleryDestroyInput struct {
type GalleryReader interface {
Find(ctx context.Context, id int) (*Gallery, error)
FindMany(ctx context.Context, ids []int) ([]*Gallery, error)
FindByChecksum(ctx context.Context, checksum string) (*Gallery, error)
FindByChecksum(ctx context.Context, checksum string) ([]*Gallery, error)
FindByChecksums(ctx context.Context, checksums []string) ([]*Gallery, error)
FindByPath(ctx context.Context, path string) (*Gallery, error)
FindByPath(ctx context.Context, path string) ([]*Gallery, error)
FindBySceneID(ctx context.Context, sceneID int) ([]*Gallery, error)
FindByImageID(ctx context.Context, imageID int) ([]*Gallery, error)
Count(ctx context.Context) (int, error)
All(ctx context.Context) ([]*Gallery, error)
Query(ctx context.Context, galleryFilter *GalleryFilterType, findFilter *FindFilterType) ([]*Gallery, int, error)
QueryCount(ctx context.Context, galleryFilter *GalleryFilterType, findFilter *FindFilterType) (int, error)
GetPerformerIDs(ctx context.Context, galleryID int) ([]int, error)
GetTagIDs(ctx context.Context, galleryID int) ([]int, error)
GetSceneIDs(ctx context.Context, galleryID int) ([]int, error)
GetImageIDs(ctx context.Context, galleryID int) ([]int, error)
}
type GalleryWriter interface {
Create(ctx context.Context, newGallery Gallery) (*Gallery, error)
Update(ctx context.Context, updatedGallery Gallery) (*Gallery, error)
UpdatePartial(ctx context.Context, updatedGallery GalleryPartial) (*Gallery, error)
UpdateFileModTime(ctx context.Context, id int, modTime NullSQLiteTimestamp) error
Create(ctx context.Context, newGallery *Gallery, fileIDs []file.ID) error
Update(ctx context.Context, updatedGallery *Gallery) error
UpdatePartial(ctx context.Context, id int, updatedGallery GalleryPartial) (*Gallery, error)
Destroy(ctx context.Context, id int) error
UpdatePerformers(ctx context.Context, galleryID int, performerIDs []int) error
UpdateTags(ctx context.Context, galleryID int, tagIDs []int) error
UpdateScenes(ctx context.Context, galleryID int, sceneIDs []int) error
UpdateImages(ctx context.Context, galleryID int, imageIDs []int) error
}

View File

@@ -92,37 +92,25 @@ type ImageReader interface {
ImageFinder
// TODO - remove this in another PR
Find(ctx context.Context, id int) (*Image, error)
FindByChecksum(ctx context.Context, checksum string) (*Image, error)
FindByChecksum(ctx context.Context, checksum string) ([]*Image, error)
FindByGalleryID(ctx context.Context, galleryID int) ([]*Image, error)
CountByGalleryID(ctx context.Context, galleryID int) (int, error)
FindByPath(ctx context.Context, path string) (*Image, error)
// FindByPerformerID(performerID int) ([]*Image, error)
// CountByPerformerID(performerID int) (int, error)
// FindByStudioID(studioID int) ([]*Image, error)
FindByPath(ctx context.Context, path string) ([]*Image, error)
Count(ctx context.Context) (int, error)
Size(ctx context.Context) (float64, error)
// SizeCount() (string, error)
// CountByStudioID(studioID int) (int, error)
// CountByTagID(tagID int) (int, error)
All(ctx context.Context) ([]*Image, error)
Query(ctx context.Context, options ImageQueryOptions) (*ImageQueryResult, error)
QueryCount(ctx context.Context, imageFilter *ImageFilterType, findFilter *FindFilterType) (int, error)
GetGalleryIDs(ctx context.Context, imageID int) ([]int, error)
GetTagIDs(ctx context.Context, imageID int) ([]int, error)
GetPerformerIDs(ctx context.Context, imageID int) ([]int, error)
}
type ImageWriter interface {
Create(ctx context.Context, newImage Image) (*Image, error)
Update(ctx context.Context, updatedImage ImagePartial) (*Image, error)
UpdateFull(ctx context.Context, updatedImage Image) (*Image, error)
Create(ctx context.Context, newImage *ImageCreateInput) error
Update(ctx context.Context, updatedImage *Image) error
UpdatePartial(ctx context.Context, id int, partial ImagePartial) (*Image, error)
IncrementOCounter(ctx context.Context, id int) (int, error)
DecrementOCounter(ctx context.Context, id int) (int, error)
ResetOCounter(ctx context.Context, id int) (int, error)
Destroy(ctx context.Context, id int) error
UpdateGalleries(ctx context.Context, imageID int, galleryIDs []int) error
UpdatePerformers(ctx context.Context, imageID int, performerIDs []int) error
UpdateTags(ctx context.Context, imageID int, tagIDs []int) error
}
type ImageReaderWriter interface {

39
pkg/models/int64.go Normal file
View File

@@ -0,0 +1,39 @@
package models
import (
"errors"
"fmt"
"io"
"strconv"
"github.com/99designs/gqlgen/graphql"
"github.com/stashapp/stash/pkg/logger"
)
var ErrInt64 = errors.New("cannot parse Int64")
func MarshalInt64(v int64) graphql.Marshaler {
return graphql.WriterFunc(func(w io.Writer) {
_, err := io.WriteString(w, strconv.FormatInt(v, 10))
if err != nil {
logger.Warnf("could not marshal int64: %v", err)
}
})
}
func UnmarshalInt64(v interface{}) (int64, error) {
if tmpStr, ok := v.(string); ok {
if len(tmpStr) == 0 {
return 0, nil
}
ret, err := strconv.ParseInt(tmpStr, 10, 64)
if err != nil {
return 0, fmt.Errorf("cannot parse %v as Int64: %w", tmpStr, err)
}
return ret, nil
}
return 0, fmt.Errorf("%w: not a string", ErrInt64)
}

View File

@@ -10,7 +10,7 @@ import (
type ImageFile struct {
ModTime json.JSONTime `json:"mod_time,omitempty"`
Size int `json:"size"`
Size int64 `json:"size"`
Width int `json:"width"`
Height int `json:"height"`
}

View File

@@ -10,34 +10,34 @@ import (
)
type Performer struct {
Name string `json:"name,omitempty"`
Gender string `json:"gender,omitempty"`
URL string `json:"url,omitempty"`
Twitter string `json:"twitter,omitempty"`
Instagram string `json:"instagram,omitempty"`
Birthdate string `json:"birthdate,omitempty"`
Ethnicity string `json:"ethnicity,omitempty"`
Country string `json:"country,omitempty"`
EyeColor string `json:"eye_color,omitempty"`
Height string `json:"height,omitempty"`
Measurements string `json:"measurements,omitempty"`
FakeTits string `json:"fake_tits,omitempty"`
CareerLength string `json:"career_length,omitempty"`
Tattoos string `json:"tattoos,omitempty"`
Piercings string `json:"piercings,omitempty"`
Aliases string `json:"aliases,omitempty"`
Favorite bool `json:"favorite,omitempty"`
Tags []string `json:"tags,omitempty"`
Image string `json:"image,omitempty"`
CreatedAt json.JSONTime `json:"created_at,omitempty"`
UpdatedAt json.JSONTime `json:"updated_at,omitempty"`
Rating int `json:"rating,omitempty"`
Details string `json:"details,omitempty"`
DeathDate string `json:"death_date,omitempty"`
HairColor string `json:"hair_color,omitempty"`
Weight int `json:"weight,omitempty"`
StashIDs []models.StashID `json:"stash_ids,omitempty"`
IgnoreAutoTag bool `json:"ignore_auto_tag,omitempty"`
Name string `json:"name,omitempty"`
Gender string `json:"gender,omitempty"`
URL string `json:"url,omitempty"`
Twitter string `json:"twitter,omitempty"`
Instagram string `json:"instagram,omitempty"`
Birthdate string `json:"birthdate,omitempty"`
Ethnicity string `json:"ethnicity,omitempty"`
Country string `json:"country,omitempty"`
EyeColor string `json:"eye_color,omitempty"`
Height string `json:"height,omitempty"`
Measurements string `json:"measurements,omitempty"`
FakeTits string `json:"fake_tits,omitempty"`
CareerLength string `json:"career_length,omitempty"`
Tattoos string `json:"tattoos,omitempty"`
Piercings string `json:"piercings,omitempty"`
Aliases string `json:"aliases,omitempty"`
Favorite bool `json:"favorite,omitempty"`
Tags []string `json:"tags,omitempty"`
Image string `json:"image,omitempty"`
CreatedAt json.JSONTime `json:"created_at,omitempty"`
UpdatedAt json.JSONTime `json:"updated_at,omitempty"`
Rating int `json:"rating,omitempty"`
Details string `json:"details,omitempty"`
DeathDate string `json:"death_date,omitempty"`
HairColor string `json:"hair_color,omitempty"`
Weight int `json:"weight,omitempty"`
StashIDs []*models.StashID `json:"stash_ids,omitempty"`
IgnoreAutoTag bool `json:"ignore_auto_tag,omitempty"`
}
func LoadPerformerFile(filePath string) (*Performer, error) {

View File

@@ -10,17 +10,17 @@ import (
)
type Studio struct {
Name string `json:"name,omitempty"`
URL string `json:"url,omitempty"`
ParentStudio string `json:"parent_studio,omitempty"`
Image string `json:"image,omitempty"`
CreatedAt json.JSONTime `json:"created_at,omitempty"`
UpdatedAt json.JSONTime `json:"updated_at,omitempty"`
Rating int `json:"rating,omitempty"`
Details string `json:"details,omitempty"`
Aliases []string `json:"aliases,omitempty"`
StashIDs []models.StashID `json:"stash_ids,omitempty"`
IgnoreAutoTag bool `json:"ignore_auto_tag,omitempty"`
Name string `json:"name,omitempty"`
URL string `json:"url,omitempty"`
ParentStudio string `json:"parent_studio,omitempty"`
Image string `json:"image,omitempty"`
CreatedAt json.JSONTime `json:"created_at,omitempty"`
UpdatedAt json.JSONTime `json:"updated_at,omitempty"`
Rating int `json:"rating,omitempty"`
Details string `json:"details,omitempty"`
Aliases []string `json:"aliases,omitempty"`
StashIDs []*models.StashID `json:"stash_ids,omitempty"`
IgnoreAutoTag bool `json:"ignore_auto_tag,omitempty"`
}
func LoadStudioFile(filePath string) (*Studio, error) {

View File

@@ -5,8 +5,10 @@ package mocks
import (
context "context"
models "github.com/stashapp/stash/pkg/models"
file "github.com/stashapp/stash/pkg/file"
mock "github.com/stretchr/testify/mock"
models "github.com/stashapp/stash/pkg/models"
)
// GalleryReaderWriter is an autogenerated mock type for the GalleryReaderWriter type
@@ -58,27 +60,18 @@ func (_m *GalleryReaderWriter) Count(ctx context.Context) (int, error) {
return r0, r1
}
// Create provides a mock function with given fields: ctx, newGallery
func (_m *GalleryReaderWriter) Create(ctx context.Context, newGallery models.Gallery) (*models.Gallery, error) {
ret := _m.Called(ctx, newGallery)
// Create provides a mock function with given fields: ctx, newGallery, fileIDs
func (_m *GalleryReaderWriter) Create(ctx context.Context, newGallery *models.Gallery, fileIDs []file.ID) error {
ret := _m.Called(ctx, newGallery, fileIDs)
var r0 *models.Gallery
if rf, ok := ret.Get(0).(func(context.Context, models.Gallery) *models.Gallery); ok {
r0 = rf(ctx, newGallery)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *models.Gallery, []file.ID) error); ok {
r0 = rf(ctx, newGallery, fileIDs)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Gallery)
}
r0 = ret.Error(0)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.Gallery) error); ok {
r1 = rf(ctx, newGallery)
} else {
r1 = ret.Error(1)
}
return r0, r1
return r0
}
// Destroy provides a mock function with given fields: ctx, id
@@ -119,15 +112,15 @@ func (_m *GalleryReaderWriter) Find(ctx context.Context, id int) (*models.Galler
}
// FindByChecksum provides a mock function with given fields: ctx, checksum
func (_m *GalleryReaderWriter) FindByChecksum(ctx context.Context, checksum string) (*models.Gallery, error) {
func (_m *GalleryReaderWriter) FindByChecksum(ctx context.Context, checksum string) ([]*models.Gallery, error) {
ret := _m.Called(ctx, checksum)
var r0 *models.Gallery
if rf, ok := ret.Get(0).(func(context.Context, string) *models.Gallery); ok {
var r0 []*models.Gallery
if rf, ok := ret.Get(0).(func(context.Context, string) []*models.Gallery); ok {
r0 = rf(ctx, checksum)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Gallery)
r0 = ret.Get(0).([]*models.Gallery)
}
}
@@ -188,15 +181,15 @@ func (_m *GalleryReaderWriter) FindByImageID(ctx context.Context, imageID int) (
}
// FindByPath provides a mock function with given fields: ctx, path
func (_m *GalleryReaderWriter) FindByPath(ctx context.Context, path string) (*models.Gallery, error) {
func (_m *GalleryReaderWriter) FindByPath(ctx context.Context, path string) ([]*models.Gallery, error) {
ret := _m.Called(ctx, path)
var r0 *models.Gallery
if rf, ok := ret.Get(0).(func(context.Context, string) *models.Gallery); ok {
var r0 []*models.Gallery
if rf, ok := ret.Get(0).(func(context.Context, string) []*models.Gallery); ok {
r0 = rf(ctx, path)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Gallery)
r0 = ret.Get(0).([]*models.Gallery)
}
}
@@ -279,75 +272,6 @@ func (_m *GalleryReaderWriter) GetImageIDs(ctx context.Context, galleryID int) (
return r0, r1
}
// GetPerformerIDs provides a mock function with given fields: ctx, galleryID
func (_m *GalleryReaderWriter) GetPerformerIDs(ctx context.Context, galleryID int) ([]int, error) {
ret := _m.Called(ctx, galleryID)
var r0 []int
if rf, ok := ret.Get(0).(func(context.Context, int) []int); ok {
r0 = rf(ctx, galleryID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]int)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
r1 = rf(ctx, galleryID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetSceneIDs provides a mock function with given fields: ctx, galleryID
func (_m *GalleryReaderWriter) GetSceneIDs(ctx context.Context, galleryID int) ([]int, error) {
ret := _m.Called(ctx, galleryID)
var r0 []int
if rf, ok := ret.Get(0).(func(context.Context, int) []int); ok {
r0 = rf(ctx, galleryID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]int)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
r1 = rf(ctx, galleryID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetTagIDs provides a mock function with given fields: ctx, galleryID
func (_m *GalleryReaderWriter) GetTagIDs(ctx context.Context, galleryID int) ([]int, error) {
ret := _m.Called(ctx, galleryID)
var r0 []int
if rf, ok := ret.Get(0).(func(context.Context, int) []int); ok {
r0 = rf(ctx, galleryID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]int)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
r1 = rf(ctx, galleryID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Query provides a mock function with given fields: ctx, galleryFilter, findFilter
func (_m *GalleryReaderWriter) Query(ctx context.Context, galleryFilter *models.GalleryFilterType, findFilter *models.FindFilterType) ([]*models.Gallery, int, error) {
ret := _m.Called(ctx, galleryFilter, findFilter)
@@ -400,35 +324,12 @@ func (_m *GalleryReaderWriter) QueryCount(ctx context.Context, galleryFilter *mo
}
// Update provides a mock function with given fields: ctx, updatedGallery
func (_m *GalleryReaderWriter) Update(ctx context.Context, updatedGallery models.Gallery) (*models.Gallery, error) {
func (_m *GalleryReaderWriter) Update(ctx context.Context, updatedGallery *models.Gallery) error {
ret := _m.Called(ctx, updatedGallery)
var r0 *models.Gallery
if rf, ok := ret.Get(0).(func(context.Context, models.Gallery) *models.Gallery); ok {
r0 = rf(ctx, updatedGallery)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Gallery)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.Gallery) error); ok {
r1 = rf(ctx, updatedGallery)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateFileModTime provides a mock function with given fields: ctx, id, modTime
func (_m *GalleryReaderWriter) UpdateFileModTime(ctx context.Context, id int, modTime models.NullSQLiteTimestamp) error {
ret := _m.Called(ctx, id, modTime)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int, models.NullSQLiteTimestamp) error); ok {
r0 = rf(ctx, id, modTime)
if rf, ok := ret.Get(0).(func(context.Context, *models.Gallery) error); ok {
r0 = rf(ctx, updatedGallery)
} else {
r0 = ret.Error(0)
}
@@ -450,13 +351,13 @@ func (_m *GalleryReaderWriter) UpdateImages(ctx context.Context, galleryID int,
return r0
}
// UpdatePartial provides a mock function with given fields: ctx, updatedGallery
func (_m *GalleryReaderWriter) UpdatePartial(ctx context.Context, updatedGallery models.GalleryPartial) (*models.Gallery, error) {
ret := _m.Called(ctx, updatedGallery)
// UpdatePartial provides a mock function with given fields: ctx, id, updatedGallery
func (_m *GalleryReaderWriter) UpdatePartial(ctx context.Context, id int, updatedGallery models.GalleryPartial) (*models.Gallery, error) {
ret := _m.Called(ctx, id, updatedGallery)
var r0 *models.Gallery
if rf, ok := ret.Get(0).(func(context.Context, models.GalleryPartial) *models.Gallery); ok {
r0 = rf(ctx, updatedGallery)
if rf, ok := ret.Get(0).(func(context.Context, int, models.GalleryPartial) *models.Gallery); ok {
r0 = rf(ctx, id, updatedGallery)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Gallery)
@@ -464,53 +365,11 @@ func (_m *GalleryReaderWriter) UpdatePartial(ctx context.Context, updatedGallery
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.GalleryPartial) error); ok {
r1 = rf(ctx, updatedGallery)
if rf, ok := ret.Get(1).(func(context.Context, int, models.GalleryPartial) error); ok {
r1 = rf(ctx, id, updatedGallery)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdatePerformers provides a mock function with given fields: ctx, galleryID, performerIDs
func (_m *GalleryReaderWriter) UpdatePerformers(ctx context.Context, galleryID int, performerIDs []int) error {
ret := _m.Called(ctx, galleryID, performerIDs)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int, []int) error); ok {
r0 = rf(ctx, galleryID, performerIDs)
} else {
r0 = ret.Error(0)
}
return r0
}
// UpdateScenes provides a mock function with given fields: ctx, galleryID, sceneIDs
func (_m *GalleryReaderWriter) UpdateScenes(ctx context.Context, galleryID int, sceneIDs []int) error {
ret := _m.Called(ctx, galleryID, sceneIDs)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int, []int) error); ok {
r0 = rf(ctx, galleryID, sceneIDs)
} else {
r0 = ret.Error(0)
}
return r0
}
// UpdateTags provides a mock function with given fields: ctx, galleryID, tagIDs
func (_m *GalleryReaderWriter) UpdateTags(ctx context.Context, galleryID int, tagIDs []int) error {
ret := _m.Called(ctx, galleryID, tagIDs)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int, []int) error); ok {
r0 = rf(ctx, galleryID, tagIDs)
} else {
r0 = ret.Error(0)
}
return r0
}

View File

@@ -80,26 +80,17 @@ func (_m *ImageReaderWriter) CountByGalleryID(ctx context.Context, galleryID int
}
// Create provides a mock function with given fields: ctx, newImage
func (_m *ImageReaderWriter) Create(ctx context.Context, newImage models.Image) (*models.Image, error) {
func (_m *ImageReaderWriter) Create(ctx context.Context, newImage *models.ImageCreateInput) error {
ret := _m.Called(ctx, newImage)
var r0 *models.Image
if rf, ok := ret.Get(0).(func(context.Context, models.Image) *models.Image); ok {
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *models.ImageCreateInput) error); ok {
r0 = rf(ctx, newImage)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Image)
}
r0 = ret.Error(0)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.Image) error); ok {
r1 = rf(ctx, newImage)
} else {
r1 = ret.Error(1)
}
return r0, r1
return r0
}
// DecrementOCounter provides a mock function with given fields: ctx, id
@@ -161,15 +152,15 @@ func (_m *ImageReaderWriter) Find(ctx context.Context, id int) (*models.Image, e
}
// FindByChecksum provides a mock function with given fields: ctx, checksum
func (_m *ImageReaderWriter) FindByChecksum(ctx context.Context, checksum string) (*models.Image, error) {
func (_m *ImageReaderWriter) FindByChecksum(ctx context.Context, checksum string) ([]*models.Image, error) {
ret := _m.Called(ctx, checksum)
var r0 *models.Image
if rf, ok := ret.Get(0).(func(context.Context, string) *models.Image); ok {
var r0 []*models.Image
if rf, ok := ret.Get(0).(func(context.Context, string) []*models.Image); ok {
r0 = rf(ctx, checksum)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Image)
r0 = ret.Get(0).([]*models.Image)
}
}
@@ -207,15 +198,15 @@ func (_m *ImageReaderWriter) FindByGalleryID(ctx context.Context, galleryID int)
}
// FindByPath provides a mock function with given fields: ctx, path
func (_m *ImageReaderWriter) FindByPath(ctx context.Context, path string) (*models.Image, error) {
func (_m *ImageReaderWriter) FindByPath(ctx context.Context, path string) ([]*models.Image, error) {
ret := _m.Called(ctx, path)
var r0 *models.Image
if rf, ok := ret.Get(0).(func(context.Context, string) *models.Image); ok {
var r0 []*models.Image
if rf, ok := ret.Get(0).(func(context.Context, string) []*models.Image); ok {
r0 = rf(ctx, path)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Image)
r0 = ret.Get(0).([]*models.Image)
}
}
@@ -252,75 +243,6 @@ func (_m *ImageReaderWriter) FindMany(ctx context.Context, ids []int) ([]*models
return r0, r1
}
// GetGalleryIDs provides a mock function with given fields: ctx, imageID
func (_m *ImageReaderWriter) GetGalleryIDs(ctx context.Context, imageID int) ([]int, error) {
ret := _m.Called(ctx, imageID)
var r0 []int
if rf, ok := ret.Get(0).(func(context.Context, int) []int); ok {
r0 = rf(ctx, imageID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]int)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
r1 = rf(ctx, imageID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetPerformerIDs provides a mock function with given fields: ctx, imageID
func (_m *ImageReaderWriter) GetPerformerIDs(ctx context.Context, imageID int) ([]int, error) {
ret := _m.Called(ctx, imageID)
var r0 []int
if rf, ok := ret.Get(0).(func(context.Context, int) []int); ok {
r0 = rf(ctx, imageID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]int)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
r1 = rf(ctx, imageID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetTagIDs provides a mock function with given fields: ctx, imageID
func (_m *ImageReaderWriter) GetTagIDs(ctx context.Context, imageID int) ([]int, error) {
ret := _m.Called(ctx, imageID)
var r0 []int
if rf, ok := ret.Get(0).(func(context.Context, int) []int); ok {
r0 = rf(ctx, imageID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]int)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
r1 = rf(ctx, imageID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// IncrementOCounter provides a mock function with given fields: ctx, id
func (_m *ImageReaderWriter) IncrementOCounter(ctx context.Context, id int) (int, error) {
ret := _m.Called(ctx, id)
@@ -429,12 +351,26 @@ func (_m *ImageReaderWriter) Size(ctx context.Context) (float64, error) {
}
// Update provides a mock function with given fields: ctx, updatedImage
func (_m *ImageReaderWriter) Update(ctx context.Context, updatedImage models.ImagePartial) (*models.Image, error) {
func (_m *ImageReaderWriter) Update(ctx context.Context, updatedImage *models.Image) error {
ret := _m.Called(ctx, updatedImage)
var r0 *models.Image
if rf, ok := ret.Get(0).(func(context.Context, models.ImagePartial) *models.Image); ok {
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *models.Image) error); ok {
r0 = rf(ctx, updatedImage)
} else {
r0 = ret.Error(0)
}
return r0
}
// UpdatePartial provides a mock function with given fields: ctx, id, partial
func (_m *ImageReaderWriter) UpdatePartial(ctx context.Context, id int, partial models.ImagePartial) (*models.Image, error) {
ret := _m.Called(ctx, id, partial)
var r0 *models.Image
if rf, ok := ret.Get(0).(func(context.Context, int, models.ImagePartial) *models.Image); ok {
r0 = rf(ctx, id, partial)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Image)
@@ -442,76 +378,11 @@ func (_m *ImageReaderWriter) Update(ctx context.Context, updatedImage models.Ima
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.ImagePartial) error); ok {
r1 = rf(ctx, updatedImage)
if rf, ok := ret.Get(1).(func(context.Context, int, models.ImagePartial) error); ok {
r1 = rf(ctx, id, partial)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateFull provides a mock function with given fields: ctx, updatedImage
func (_m *ImageReaderWriter) UpdateFull(ctx context.Context, updatedImage models.Image) (*models.Image, error) {
ret := _m.Called(ctx, updatedImage)
var r0 *models.Image
if rf, ok := ret.Get(0).(func(context.Context, models.Image) *models.Image); ok {
r0 = rf(ctx, updatedImage)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Image)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.Image) error); ok {
r1 = rf(ctx, updatedImage)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateGalleries provides a mock function with given fields: ctx, imageID, galleryIDs
func (_m *ImageReaderWriter) UpdateGalleries(ctx context.Context, imageID int, galleryIDs []int) error {
ret := _m.Called(ctx, imageID, galleryIDs)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int, []int) error); ok {
r0 = rf(ctx, imageID, galleryIDs)
} else {
r0 = ret.Error(0)
}
return r0
}
// UpdatePerformers provides a mock function with given fields: ctx, imageID, performerIDs
func (_m *ImageReaderWriter) UpdatePerformers(ctx context.Context, imageID int, performerIDs []int) error {
ret := _m.Called(ctx, imageID, performerIDs)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int, []int) error); ok {
r0 = rf(ctx, imageID, performerIDs)
} else {
r0 = ret.Error(0)
}
return r0
}
// UpdateTags provides a mock function with given fields: ctx, imageID, tagIDs
func (_m *ImageReaderWriter) UpdateTags(ctx context.Context, imageID int, tagIDs []int) error {
ret := _m.Called(ctx, imageID, tagIDs)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int, []int) error); ok {
r0 = rf(ctx, imageID, tagIDs)
} else {
r0 = ret.Error(0)
}
return r0
}

View File

@@ -520,11 +520,11 @@ func (_m *PerformerReaderWriter) UpdateImage(ctx context.Context, performerID in
}
// UpdateStashIDs provides a mock function with given fields: ctx, performerID, stashIDs
func (_m *PerformerReaderWriter) UpdateStashIDs(ctx context.Context, performerID int, stashIDs []models.StashID) error {
func (_m *PerformerReaderWriter) UpdateStashIDs(ctx context.Context, performerID int, stashIDs []*models.StashID) error {
ret := _m.Called(ctx, performerID, stashIDs)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int, []models.StashID) error); ok {
if rf, ok := ret.Get(0).(func(context.Context, int, []*models.StashID) error); ok {
r0 = rf(ctx, performerID, stashIDs)
} else {
r0 = ret.Error(0)

View File

@@ -5,8 +5,10 @@ package mocks
import (
context "context"
models "github.com/stashapp/stash/pkg/models"
file "github.com/stashapp/stash/pkg/file"
mock "github.com/stretchr/testify/mock"
models "github.com/stashapp/stash/pkg/models"
)
// SceneReaderWriter is an autogenerated mock type for the SceneReaderWriter type
@@ -184,27 +186,18 @@ func (_m *SceneReaderWriter) CountMissingOSHash(ctx context.Context) (int, error
return r0, r1
}
// Create provides a mock function with given fields: ctx, newScene
func (_m *SceneReaderWriter) Create(ctx context.Context, newScene models.Scene) (*models.Scene, error) {
ret := _m.Called(ctx, newScene)
// Create provides a mock function with given fields: ctx, newScene, fileIDs
func (_m *SceneReaderWriter) Create(ctx context.Context, newScene *models.Scene, fileIDs []file.ID) error {
ret := _m.Called(ctx, newScene, fileIDs)
var r0 *models.Scene
if rf, ok := ret.Get(0).(func(context.Context, models.Scene) *models.Scene); ok {
r0 = rf(ctx, newScene)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *models.Scene, []file.ID) error); ok {
r0 = rf(ctx, newScene, fileIDs)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Scene)
}
r0 = ret.Error(0)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.Scene) error); ok {
r1 = rf(ctx, newScene)
} else {
r1 = ret.Error(1)
}
return r0, r1
return r0
}
// DecrementOCounter provides a mock function with given fields: ctx, id
@@ -301,15 +294,15 @@ func (_m *SceneReaderWriter) Find(ctx context.Context, id int) (*models.Scene, e
}
// FindByChecksum provides a mock function with given fields: ctx, checksum
func (_m *SceneReaderWriter) FindByChecksum(ctx context.Context, checksum string) (*models.Scene, error) {
func (_m *SceneReaderWriter) FindByChecksum(ctx context.Context, checksum string) ([]*models.Scene, error) {
ret := _m.Called(ctx, checksum)
var r0 *models.Scene
if rf, ok := ret.Get(0).(func(context.Context, string) *models.Scene); ok {
var r0 []*models.Scene
if rf, ok := ret.Get(0).(func(context.Context, string) []*models.Scene); ok {
r0 = rf(ctx, checksum)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Scene)
r0 = ret.Get(0).([]*models.Scene)
}
}
@@ -370,15 +363,15 @@ func (_m *SceneReaderWriter) FindByMovieID(ctx context.Context, movieID int) ([]
}
// FindByOSHash provides a mock function with given fields: ctx, oshash
func (_m *SceneReaderWriter) FindByOSHash(ctx context.Context, oshash string) (*models.Scene, error) {
func (_m *SceneReaderWriter) FindByOSHash(ctx context.Context, oshash string) ([]*models.Scene, error) {
ret := _m.Called(ctx, oshash)
var r0 *models.Scene
if rf, ok := ret.Get(0).(func(context.Context, string) *models.Scene); ok {
var r0 []*models.Scene
if rf, ok := ret.Get(0).(func(context.Context, string) []*models.Scene); ok {
r0 = rf(ctx, oshash)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Scene)
r0 = ret.Get(0).([]*models.Scene)
}
}
@@ -393,15 +386,15 @@ func (_m *SceneReaderWriter) FindByOSHash(ctx context.Context, oshash string) (*
}
// FindByPath provides a mock function with given fields: ctx, path
func (_m *SceneReaderWriter) FindByPath(ctx context.Context, path string) (*models.Scene, error) {
func (_m *SceneReaderWriter) FindByPath(ctx context.Context, path string) ([]*models.Scene, error) {
ret := _m.Called(ctx, path)
var r0 *models.Scene
if rf, ok := ret.Get(0).(func(context.Context, string) *models.Scene); ok {
var r0 []*models.Scene
if rf, ok := ret.Get(0).(func(context.Context, string) []*models.Scene); ok {
r0 = rf(ctx, path)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Scene)
r0 = ret.Get(0).([]*models.Scene)
}
}
@@ -484,29 +477,6 @@ func (_m *SceneReaderWriter) FindMany(ctx context.Context, ids []int) ([]*models
return r0, r1
}
// GetCaptions provides a mock function with given fields: ctx, sceneID
func (_m *SceneReaderWriter) GetCaptions(ctx context.Context, sceneID int) ([]*models.SceneCaption, error) {
ret := _m.Called(ctx, sceneID)
var r0 []*models.SceneCaption
if rf, ok := ret.Get(0).(func(context.Context, int) []*models.SceneCaption); ok {
r0 = rf(ctx, sceneID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*models.SceneCaption)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
r1 = rf(ctx, sceneID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetCover provides a mock function with given fields: ctx, sceneID
func (_m *SceneReaderWriter) GetCover(ctx context.Context, sceneID int) ([]byte, error) {
ret := _m.Called(ctx, sceneID)
@@ -530,121 +500,6 @@ func (_m *SceneReaderWriter) GetCover(ctx context.Context, sceneID int) ([]byte,
return r0, r1
}
// GetGalleryIDs provides a mock function with given fields: ctx, sceneID
func (_m *SceneReaderWriter) GetGalleryIDs(ctx context.Context, sceneID int) ([]int, error) {
ret := _m.Called(ctx, sceneID)
var r0 []int
if rf, ok := ret.Get(0).(func(context.Context, int) []int); ok {
r0 = rf(ctx, sceneID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]int)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
r1 = rf(ctx, sceneID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetMovies provides a mock function with given fields: ctx, sceneID
func (_m *SceneReaderWriter) GetMovies(ctx context.Context, sceneID int) ([]models.MoviesScenes, error) {
ret := _m.Called(ctx, sceneID)
var r0 []models.MoviesScenes
if rf, ok := ret.Get(0).(func(context.Context, int) []models.MoviesScenes); ok {
r0 = rf(ctx, sceneID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]models.MoviesScenes)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
r1 = rf(ctx, sceneID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetPerformerIDs provides a mock function with given fields: ctx, sceneID
func (_m *SceneReaderWriter) GetPerformerIDs(ctx context.Context, sceneID int) ([]int, error) {
ret := _m.Called(ctx, sceneID)
var r0 []int
if rf, ok := ret.Get(0).(func(context.Context, int) []int); ok {
r0 = rf(ctx, sceneID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]int)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
r1 = rf(ctx, sceneID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetStashIDs provides a mock function with given fields: ctx, sceneID
func (_m *SceneReaderWriter) GetStashIDs(ctx context.Context, sceneID int) ([]*models.StashID, error) {
ret := _m.Called(ctx, sceneID)
var r0 []*models.StashID
if rf, ok := ret.Get(0).(func(context.Context, int) []*models.StashID); ok {
r0 = rf(ctx, sceneID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*models.StashID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
r1 = rf(ctx, sceneID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetTagIDs provides a mock function with given fields: ctx, sceneID
func (_m *SceneReaderWriter) GetTagIDs(ctx context.Context, sceneID int) ([]int, error) {
ret := _m.Called(ctx, sceneID)
var r0 []int
if rf, ok := ret.Get(0).(func(context.Context, int) []int); ok {
r0 = rf(ctx, sceneID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]int)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
r1 = rf(ctx, sceneID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// IncrementOCounter provides a mock function with given fields: ctx, id
func (_m *SceneReaderWriter) IncrementOCounter(ctx context.Context, id int) (int, error) {
ret := _m.Called(ctx, id)
@@ -732,35 +587,12 @@ func (_m *SceneReaderWriter) Size(ctx context.Context) (float64, error) {
}
// Update provides a mock function with given fields: ctx, updatedScene
func (_m *SceneReaderWriter) Update(ctx context.Context, updatedScene models.ScenePartial) (*models.Scene, error) {
func (_m *SceneReaderWriter) Update(ctx context.Context, updatedScene *models.Scene) error {
ret := _m.Called(ctx, updatedScene)
var r0 *models.Scene
if rf, ok := ret.Get(0).(func(context.Context, models.ScenePartial) *models.Scene); ok {
r0 = rf(ctx, updatedScene)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Scene)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.ScenePartial) error); ok {
r1 = rf(ctx, updatedScene)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateCaptions provides a mock function with given fields: ctx, id, captions
func (_m *SceneReaderWriter) UpdateCaptions(ctx context.Context, id int, captions []*models.SceneCaption) error {
ret := _m.Called(ctx, id, captions)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int, []*models.SceneCaption) error); ok {
r0 = rf(ctx, id, captions)
if rf, ok := ret.Get(0).(func(context.Context, *models.Scene) error); ok {
r0 = rf(ctx, updatedScene)
} else {
r0 = ret.Error(0)
}
@@ -782,27 +614,13 @@ func (_m *SceneReaderWriter) UpdateCover(ctx context.Context, sceneID int, cover
return r0
}
// UpdateFileModTime provides a mock function with given fields: ctx, id, modTime
func (_m *SceneReaderWriter) UpdateFileModTime(ctx context.Context, id int, modTime models.NullSQLiteTimestamp) error {
ret := _m.Called(ctx, id, modTime)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int, models.NullSQLiteTimestamp) error); ok {
r0 = rf(ctx, id, modTime)
} else {
r0 = ret.Error(0)
}
return r0
}
// UpdateFull provides a mock function with given fields: ctx, updatedScene
func (_m *SceneReaderWriter) UpdateFull(ctx context.Context, updatedScene models.Scene) (*models.Scene, error) {
ret := _m.Called(ctx, updatedScene)
// UpdatePartial provides a mock function with given fields: ctx, id, updatedScene
func (_m *SceneReaderWriter) UpdatePartial(ctx context.Context, id int, updatedScene models.ScenePartial) (*models.Scene, error) {
ret := _m.Called(ctx, id, updatedScene)
var r0 *models.Scene
if rf, ok := ret.Get(0).(func(context.Context, models.Scene) *models.Scene); ok {
r0 = rf(ctx, updatedScene)
if rf, ok := ret.Get(0).(func(context.Context, int, models.ScenePartial) *models.Scene); ok {
r0 = rf(ctx, id, updatedScene)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Scene)
@@ -810,8 +628,8 @@ func (_m *SceneReaderWriter) UpdateFull(ctx context.Context, updatedScene models
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.Scene) error); ok {
r1 = rf(ctx, updatedScene)
if rf, ok := ret.Get(1).(func(context.Context, int, models.ScenePartial) error); ok {
r1 = rf(ctx, id, updatedScene)
} else {
r1 = ret.Error(1)
}
@@ -819,76 +637,6 @@ func (_m *SceneReaderWriter) UpdateFull(ctx context.Context, updatedScene models
return r0, r1
}
// UpdateGalleries provides a mock function with given fields: ctx, sceneID, galleryIDs
func (_m *SceneReaderWriter) UpdateGalleries(ctx context.Context, sceneID int, galleryIDs []int) error {
ret := _m.Called(ctx, sceneID, galleryIDs)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int, []int) error); ok {
r0 = rf(ctx, sceneID, galleryIDs)
} else {
r0 = ret.Error(0)
}
return r0
}
// UpdateMovies provides a mock function with given fields: ctx, sceneID, movies
func (_m *SceneReaderWriter) UpdateMovies(ctx context.Context, sceneID int, movies []models.MoviesScenes) error {
ret := _m.Called(ctx, sceneID, movies)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int, []models.MoviesScenes) error); ok {
r0 = rf(ctx, sceneID, movies)
} else {
r0 = ret.Error(0)
}
return r0
}
// UpdatePerformers provides a mock function with given fields: ctx, sceneID, performerIDs
func (_m *SceneReaderWriter) UpdatePerformers(ctx context.Context, sceneID int, performerIDs []int) error {
ret := _m.Called(ctx, sceneID, performerIDs)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int, []int) error); ok {
r0 = rf(ctx, sceneID, performerIDs)
} else {
r0 = ret.Error(0)
}
return r0
}
// UpdateStashIDs provides a mock function with given fields: ctx, sceneID, stashIDs
func (_m *SceneReaderWriter) UpdateStashIDs(ctx context.Context, sceneID int, stashIDs []models.StashID) error {
ret := _m.Called(ctx, sceneID, stashIDs)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int, []models.StashID) error); ok {
r0 = rf(ctx, sceneID, stashIDs)
} else {
r0 = ret.Error(0)
}
return r0
}
// UpdateTags provides a mock function with given fields: ctx, sceneID, tagIDs
func (_m *SceneReaderWriter) UpdateTags(ctx context.Context, sceneID int, tagIDs []int) error {
ret := _m.Called(ctx, sceneID, tagIDs)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int, []int) error); ok {
r0 = rf(ctx, sceneID, tagIDs)
} else {
r0 = ret.Error(0)
}
return r0
}
// Wall provides a mock function with given fields: ctx, q
func (_m *SceneReaderWriter) Wall(ctx context.Context, q *string) ([]*models.Scene, error) {
ret := _m.Called(ctx, q)

View File

@@ -442,11 +442,11 @@ func (_m *StudioReaderWriter) UpdateImage(ctx context.Context, studioID int, ima
}
// UpdateStashIDs provides a mock function with given fields: ctx, studioID, stashIDs
func (_m *StudioReaderWriter) UpdateStashIDs(ctx context.Context, studioID int, stashIDs []models.StashID) error {
func (_m *StudioReaderWriter) UpdateStashIDs(ctx context.Context, studioID int, stashIDs []*models.StashID) error {
ret := _m.Called(ctx, studioID, stashIDs)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int, []models.StashID) error); ok {
if rf, ok := ret.Get(0).(func(context.Context, int, []*models.StashID) error); ok {
r0 = rf(ctx, studioID, stashIDs)
} else {
r0 = ret.Error(0)

View File

@@ -4,6 +4,7 @@ import (
context "context"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/txn"
)
type TxnManager struct{}
@@ -12,6 +13,10 @@ func (*TxnManager) Begin(ctx context.Context) (context.Context, error) {
return ctx, nil
}
func (*TxnManager) WithDatabase(ctx context.Context) (context.Context, error) {
return ctx, nil
}
func (*TxnManager) Commit(ctx context.Context) error {
return nil
}
@@ -20,6 +25,12 @@ func (*TxnManager) Rollback(ctx context.Context) error {
return nil
}
func (*TxnManager) AddPostCommitHook(ctx context.Context, hook txn.TxnFunc) {
}
func (*TxnManager) AddPostRollbackHook(ctx context.Context, hook txn.TxnFunc) {
}
func (*TxnManager) Reset() error {
return nil
}

View File

@@ -1,89 +1,115 @@
package models
import (
"database/sql"
"path/filepath"
"time"
"github.com/stashapp/stash/pkg/file"
)
type Gallery struct {
ID int `db:"id" json:"id"`
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"`
Organized bool `db:"organized" json:"organized"`
StudioID sql.NullInt64 `db:"studio_id,omitempty" json:"studio_id"`
FileModTime NullSQLiteTimestamp `db:"file_mod_time" json:"file_mod_time"`
CreatedAt SQLiteTimestamp `db:"created_at" json:"created_at"`
UpdatedAt SQLiteTimestamp `db:"updated_at" json:"updated_at"`
ID int `json:"id"`
// Path *string `json:"path"`
// Checksum string `json:"checksum"`
// Zip bool `json:"zip"`
Title string `json:"title"`
URL string `json:"url"`
Date *Date `json:"date"`
Details string `json:"details"`
Rating *int `json:"rating"`
Organized bool `json:"organized"`
StudioID *int `json:"studio_id"`
// FileModTime *time.Time `json:"file_mod_time"`
// transient - not persisted
Files []file.File
FolderID *file.FolderID `json:"folder_id"`
// transient - not persisted
FolderPath string `json:"folder_path"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
SceneIDs []int `json:"scene_ids"`
TagIDs []int `json:"tag_ids"`
PerformerIDs []int `json:"performer_ids"`
}
func (g Gallery) PrimaryFile() file.File {
if len(g.Files) == 0 {
return nil
}
return g.Files[0]
}
func (g Gallery) Path() string {
if p := g.PrimaryFile(); p != nil {
return p.Base().Path
}
return g.FolderPath
}
func (g Gallery) Checksum() string {
if p := g.PrimaryFile(); p != nil {
v := p.Base().Fingerprints.Get(file.FingerprintTypeMD5)
if v == nil {
return ""
}
return v.(string)
}
return ""
}
// 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"`
Organized *bool `db:"organized" json:"organized"`
StudioID *sql.NullInt64 `db:"studio_id,omitempty" json:"studio_id"`
FileModTime *NullSQLiteTimestamp `db:"file_mod_time" json:"file_mod_time"`
CreatedAt *SQLiteTimestamp `db:"created_at" json:"created_at"`
UpdatedAt *SQLiteTimestamp `db:"updated_at" json:"updated_at"`
// Path OptionalString
// Checksum OptionalString
// Zip OptionalBool
Title OptionalString
URL OptionalString
Date OptionalDate
Details OptionalString
Rating OptionalInt
Organized OptionalBool
StudioID OptionalInt
// FileModTime OptionalTime
CreatedAt OptionalTime
UpdatedAt OptionalTime
SceneIDs *UpdateIDs
TagIDs *UpdateIDs
PerformerIDs *UpdateIDs
}
func (s *Gallery) File() File {
ret := File{
Path: s.Path.String,
}
ret.Checksum = s.Checksum
if s.FileModTime.Valid {
ret.FileModTime = s.FileModTime.Timestamp
}
return ret
}
func (s *Gallery) SetFile(f File) {
path := f.Path
s.Path = sql.NullString{
String: path,
Valid: true,
}
if f.Checksum != "" {
s.Checksum = f.Checksum
}
zeroTime := time.Time{}
if f.FileModTime != zeroTime {
s.FileModTime = NullSQLiteTimestamp{
Timestamp: f.FileModTime,
Valid: true,
}
func NewGalleryPartial() GalleryPartial {
updatedTime := time.Now()
return GalleryPartial{
UpdatedAt: NewOptionalTime(updatedTime),
}
}
// GetTitle returns the title of the scene. If the Title field is empty,
// then the base filename is returned.
func (s Gallery) GetTitle() string {
if s.Title.String != "" {
return s.Title.String
func (g Gallery) GetTitle() string {
if g.Title != "" {
return g.Title
}
if s.Path.Valid {
return filepath.Base(s.Path.String)
if len(g.Files) > 0 {
return filepath.Base(g.Path())
}
if g.FolderPath != "" {
return g.FolderPath
}
return ""

View File

@@ -1,104 +1,98 @@
package models
import (
"database/sql"
"path/filepath"
"strconv"
"time"
"github.com/stashapp/stash/pkg/file"
)
// 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"`
Organized bool `db:"organized" json:"organized"`
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"`
FileModTime NullSQLiteTimestamp `db:"file_mod_time" json:"file_mod_time"`
CreatedAt SQLiteTimestamp `db:"created_at" json:"created_at"`
UpdatedAt SQLiteTimestamp `db:"updated_at" json:"updated_at"`
ID int `json:"id"`
Title string `json:"title"`
Rating *int `json:"rating"`
Organized bool `json:"organized"`
OCounter int `json:"o_counter"`
StudioID *int `json:"studio_id"`
// transient - not persisted
Files []*file.ImageFile
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
GalleryIDs []int `json:"gallery_ids"`
TagIDs []int `json:"tag_ids"`
PerformerIDs []int `json:"performer_ids"`
}
// 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"`
Organized *bool `db:"organized" json:"organized"`
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"`
FileModTime *NullSQLiteTimestamp `db:"file_mod_time" json:"file_mod_time"`
CreatedAt *SQLiteTimestamp `db:"created_at" json:"created_at"`
UpdatedAt *SQLiteTimestamp `db:"updated_at" json:"updated_at"`
func (i Image) PrimaryFile() *file.ImageFile {
if len(i.Files) == 0 {
return nil
}
return i.Files[0]
}
func (i *Image) File() File {
ret := File{
Path: i.Path,
func (i Image) Path() string {
if p := i.PrimaryFile(); p != nil {
return p.Path
}
ret.Checksum = i.Checksum
if i.FileModTime.Valid {
ret.FileModTime = i.FileModTime.Timestamp
}
if i.Size.Valid {
ret.Size = strconv.FormatInt(i.Size.Int64, 10)
}
return ret
return ""
}
func (i *Image) SetFile(f File) {
path := f.Path
i.Path = path
if f.Checksum != "" {
i.Checksum = f.Checksum
}
zeroTime := time.Time{}
if f.FileModTime != zeroTime {
i.FileModTime = NullSQLiteTimestamp{
Timestamp: f.FileModTime,
Valid: true,
}
}
if f.Size != "" {
size, err := strconv.ParseInt(f.Size, 10, 64)
if err == nil {
i.Size = sql.NullInt64{
Int64: size,
Valid: true,
}
func (i Image) Checksum() string {
if p := i.PrimaryFile(); p != nil {
v := p.Fingerprints.Get(file.FingerprintTypeMD5)
if v == nil {
return ""
}
return v.(string)
}
return ""
}
// GetTitle returns the title of the image. If the Title field is empty,
// then the base filename is returned.
func (i *Image) GetTitle() string {
if i.Title.String != "" {
return i.Title.String
func (i Image) GetTitle() string {
if i.Title != "" {
return i.Title
}
return filepath.Base(i.Path)
if p := i.PrimaryFile(); p != nil {
return p.Basename
}
return ""
}
// 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"`
type ImageCreateInput struct {
*Image
FileIDs []file.ID
}
type ImagePartial struct {
Title OptionalString
Rating OptionalInt
Organized OptionalBool
OCounter OptionalInt
StudioID OptionalInt
CreatedAt OptionalTime
UpdatedAt OptionalTime
GalleryIDs *UpdateIDs
TagIDs *UpdateIDs
PerformerIDs *UpdateIDs
}
func NewImagePartial() ImagePartial {
updatedTime := time.Now()
return ImagePartial{
UpdatedAt: NewOptionalTime(updatedTime),
}
}
type Images []*Image

View File

@@ -1,21 +1,62 @@
package models
import "database/sql"
import (
"fmt"
"strconv"
)
type MoviesScenes struct {
MovieID int `db:"movie_id" json:"movie_id"`
SceneID int `db:"scene_id" json:"scene_id"`
SceneIndex sql.NullInt64 `db:"scene_index" json:"scene_index"`
MovieID int `json:"movie_id"`
// SceneID int `json:"scene_id"`
SceneIndex *int `json:"scene_index"`
}
type StashID struct {
StashID string `db:"stash_id" json:"stash_id"`
Endpoint string `db:"endpoint" json:"endpoint"`
}
func (s StashID) StashIDInput() StashIDInput {
return StashIDInput{
Endpoint: s.Endpoint,
StashID: s.StashID,
func (s MoviesScenes) SceneMovieInput() *SceneMovieInput {
return &SceneMovieInput{
MovieID: strconv.Itoa(s.MovieID),
SceneIndex: s.SceneIndex,
}
}
func (s MoviesScenes) Equal(o MoviesScenes) bool {
return o.MovieID == s.MovieID && ((o.SceneIndex == nil && s.SceneIndex == nil) ||
(o.SceneIndex != nil && s.SceneIndex != nil && *o.SceneIndex == *s.SceneIndex))
}
type UpdateMovieIDs struct {
Movies []MoviesScenes `json:"movies"`
Mode RelationshipUpdateMode `json:"mode"`
}
func (u *UpdateMovieIDs) SceneMovieInputs() []*SceneMovieInput {
if u == nil {
return nil
}
ret := make([]*SceneMovieInput, len(u.Movies))
for _, id := range u.Movies {
ret = append(ret, id.SceneMovieInput())
}
return ret
}
func UpdateMovieIDsFromInput(i []*SceneMovieInput) (*UpdateMovieIDs, error) {
ret := &UpdateMovieIDs{
Mode: RelationshipUpdateModeSet,
}
for _, v := range i {
mID, err := strconv.Atoi(v.MovieID)
if err != nil {
return nil, fmt.Errorf("invalid movie ID: %s", v.MovieID)
}
ret.Movies = append(ret.Movies, MoviesScenes{
MovieID: mID,
SceneIndex: v.SceneIndex,
})
}
return ret, nil
}

View File

@@ -1,125 +1,144 @@
package models
import (
"database/sql"
"path/filepath"
"strconv"
"time"
"github.com/stashapp/stash/pkg/file"
)
// Scene stores the metadata for a single video scene.
type Scene struct {
ID int `db:"id" json:"id"`
Checksum sql.NullString `db:"checksum" json:"checksum"`
OSHash sql.NullString `db:"oshash" json:"oshash"`
Path string `db:"path" json:"path"`
Title sql.NullString `db:"title" json:"title"`
Details sql.NullString `db:"details" json:"details"`
URL sql.NullString `db:"url" json:"url"`
Date SQLiteDate `db:"date" json:"date"`
Rating sql.NullInt64 `db:"rating" json:"rating"`
Organized bool `db:"organized" json:"organized"`
OCounter int `db:"o_counter" json:"o_counter"`
Size sql.NullString `db:"size" json:"size"`
Duration sql.NullFloat64 `db:"duration" json:"duration"`
VideoCodec sql.NullString `db:"video_codec" json:"video_codec"`
Format sql.NullString `db:"format" json:"format_name"`
AudioCodec sql.NullString `db:"audio_codec" json:"audio_codec"`
Width sql.NullInt64 `db:"width" json:"width"`
Height sql.NullInt64 `db:"height" json:"height"`
Framerate sql.NullFloat64 `db:"framerate" json:"framerate"`
Bitrate sql.NullInt64 `db:"bitrate" json:"bitrate"`
StudioID sql.NullInt64 `db:"studio_id,omitempty" json:"studio_id"`
FileModTime NullSQLiteTimestamp `db:"file_mod_time" json:"file_mod_time"`
Phash sql.NullInt64 `db:"phash,omitempty" json:"phash"`
CreatedAt SQLiteTimestamp `db:"created_at" json:"created_at"`
UpdatedAt SQLiteTimestamp `db:"updated_at" json:"updated_at"`
Interactive bool `db:"interactive" json:"interactive"`
InteractiveSpeed sql.NullInt64 `db:"interactive_speed" json:"interactive_speed"`
ID int `json:"id"`
Title string `json:"title"`
Details string `json:"details"`
URL string `json:"url"`
Date *Date `json:"date"`
Rating *int `json:"rating"`
Organized bool `json:"organized"`
OCounter int `json:"o_counter"`
StudioID *int `json:"studio_id"`
// transient - not persisted
Files []*file.VideoFile
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
GalleryIDs []int `json:"gallery_ids"`
TagIDs []int `json:"tag_ids"`
PerformerIDs []int `json:"performer_ids"`
Movies []MoviesScenes `json:"movies"`
StashIDs []StashID `json:"stash_ids"`
}
func (s *Scene) File() File {
ret := File{
Path: s.Path,
func (s Scene) PrimaryFile() *file.VideoFile {
if len(s.Files) == 0 {
return nil
}
if s.Checksum.Valid {
ret.Checksum = s.Checksum.String
}
if s.OSHash.Valid {
ret.OSHash = s.OSHash.String
}
if s.FileModTime.Valid {
ret.FileModTime = s.FileModTime.Timestamp
}
if s.Size.Valid {
ret.Size = s.Size.String
}
return ret
return s.Files[0]
}
func (s *Scene) SetFile(f File) {
path := f.Path
s.Path = path
func (s Scene) Path() string {
if p := s.PrimaryFile(); p != nil {
return p.Base().Path
}
if f.Checksum != "" {
s.Checksum = sql.NullString{
String: f.Checksum,
Valid: true,
return ""
}
func (s Scene) getHash(type_ string) string {
if p := s.PrimaryFile(); p != nil {
v := p.Base().Fingerprints.Get(type_)
if v == nil {
return ""
}
return v.(string)
}
if f.OSHash != "" {
s.OSHash = sql.NullString{
String: f.OSHash,
Valid: true,
return ""
}
func (s Scene) Checksum() string {
return s.getHash(file.FingerprintTypeMD5)
}
func (s Scene) OSHash() string {
return s.getHash(file.FingerprintTypeOshash)
}
func (s Scene) Phash() int64 {
if p := s.PrimaryFile(); p != nil {
v := p.Base().Fingerprints.Get(file.FingerprintTypePhash)
if v == nil {
return 0
}
return v.(int64)
}
zeroTime := time.Time{}
if f.FileModTime != zeroTime {
s.FileModTime = NullSQLiteTimestamp{
Timestamp: f.FileModTime,
Valid: true,
}
return 0
}
func (s Scene) Duration() float64 {
if p := s.PrimaryFile(); p != nil {
return p.Duration
}
if f.Size != "" {
s.Size = sql.NullString{
String: f.Size,
Valid: true,
}
return 0
}
func (s Scene) Format() string {
if p := s.PrimaryFile(); p != nil {
return p.Format
}
return ""
}
func (s Scene) VideoCodec() string {
if p := s.PrimaryFile(); p != nil {
return p.VideoCodec
}
return ""
}
func (s Scene) AudioCodec() string {
if p := s.PrimaryFile(); p != nil {
return p.AudioCodec
}
return ""
}
// ScenePartial represents part of a Scene object. It is used to update
// the database entry. Only non-nil fields will be updated.
// the database entry.
type ScenePartial struct {
ID int `db:"id" json:"id"`
Checksum *sql.NullString `db:"checksum" json:"checksum"`
OSHash *sql.NullString `db:"oshash" json:"oshash"`
Path *string `db:"path" json:"path"`
Title *sql.NullString `db:"title" json:"title"`
Details *sql.NullString `db:"details" json:"details"`
URL *sql.NullString `db:"url" json:"url"`
Date *SQLiteDate `db:"date" json:"date"`
Rating *sql.NullInt64 `db:"rating" json:"rating"`
Organized *bool `db:"organized" json:"organized"`
Size *sql.NullString `db:"size" json:"size"`
Duration *sql.NullFloat64 `db:"duration" json:"duration"`
VideoCodec *sql.NullString `db:"video_codec" json:"video_codec"`
Format *sql.NullString `db:"format" json:"format_name"`
AudioCodec *sql.NullString `db:"audio_codec" json:"audio_codec"`
Width *sql.NullInt64 `db:"width" json:"width"`
Height *sql.NullInt64 `db:"height" json:"height"`
Framerate *sql.NullFloat64 `db:"framerate" json:"framerate"`
Bitrate *sql.NullInt64 `db:"bitrate" json:"bitrate"`
StudioID *sql.NullInt64 `db:"studio_id,omitempty" json:"studio_id"`
MovieID *sql.NullInt64 `db:"movie_id,omitempty" json:"movie_id"`
FileModTime *NullSQLiteTimestamp `db:"file_mod_time" json:"file_mod_time"`
Phash *sql.NullInt64 `db:"phash,omitempty" json:"phash"`
CreatedAt *SQLiteTimestamp `db:"created_at" json:"created_at"`
UpdatedAt *SQLiteTimestamp `db:"updated_at" json:"updated_at"`
Interactive *bool `db:"interactive" json:"interactive"`
InteractiveSpeed *sql.NullInt64 `db:"interactive_speed" json:"interactive_speed"`
Title OptionalString
Details OptionalString
URL OptionalString
Date OptionalDate
Rating OptionalInt
Organized OptionalBool
OCounter OptionalInt
StudioID OptionalInt
CreatedAt OptionalTime
UpdatedAt OptionalTime
GalleryIDs *UpdateIDs
TagIDs *UpdateIDs
PerformerIDs *UpdateIDs
MovieIDs *UpdateMovieIDs
StashIDs *UpdateStashIDs
}
func NewScenePartial() ScenePartial {
updatedTime := time.Now()
return ScenePartial{
UpdatedAt: NewOptionalTime(updatedTime),
}
}
type SceneMovieInput struct {
@@ -142,86 +161,83 @@ type SceneUpdateInput struct {
Movies []*SceneMovieInput `json:"movies"`
TagIds []string `json:"tag_ids"`
// This should be a URL or a base64 encoded data URL
CoverImage *string `json:"cover_image"`
StashIds []*StashIDInput `json:"stash_ids"`
CoverImage *string `json:"cover_image"`
StashIds []StashID `json:"stash_ids"`
}
// UpdateInput constructs a SceneUpdateInput using the populated fields in the ScenePartial object.
func (s ScenePartial) UpdateInput() SceneUpdateInput {
boolPtrCopy := func(v *bool) *bool {
if v == nil {
return nil
}
func (s ScenePartial) UpdateInput(id int) SceneUpdateInput {
var dateStr *string
if s.Date.Set {
d := s.Date.Value
v := d.String()
dateStr = &v
}
vv := *v
return &vv
var stashIDs []StashID
if s.StashIDs != nil {
stashIDs = s.StashIDs.StashIDs
}
return SceneUpdateInput{
ID: strconv.Itoa(s.ID),
Title: nullStringPtrToStringPtr(s.Title),
Details: nullStringPtrToStringPtr(s.Details),
URL: nullStringPtrToStringPtr(s.URL),
Date: s.Date.StringPtr(),
Rating: nullInt64PtrToIntPtr(s.Rating),
Organized: boolPtrCopy(s.Organized),
StudioID: nullInt64PtrToStringPtr(s.StudioID),
}
}
func (s *ScenePartial) SetFile(f File) {
path := f.Path
s.Path = &path
if f.Checksum != "" {
s.Checksum = &sql.NullString{
String: f.Checksum,
Valid: true,
}
}
if f.OSHash != "" {
s.OSHash = &sql.NullString{
String: f.OSHash,
Valid: true,
}
}
zeroTime := time.Time{}
if f.FileModTime != zeroTime {
s.FileModTime = &NullSQLiteTimestamp{
Timestamp: f.FileModTime,
Valid: true,
}
}
if f.Size != "" {
s.Size = &sql.NullString{
String: f.Size,
Valid: true,
}
ID: strconv.Itoa(id),
Title: s.Title.Ptr(),
Details: s.Details.Ptr(),
URL: s.URL.Ptr(),
Date: dateStr,
Rating: s.Rating.Ptr(),
Organized: s.Organized.Ptr(),
StudioID: s.StudioID.StringPtr(),
GalleryIds: s.GalleryIDs.IDStrings(),
PerformerIds: s.PerformerIDs.IDStrings(),
Movies: s.MovieIDs.SceneMovieInputs(),
TagIds: s.TagIDs.IDStrings(),
StashIds: stashIDs,
}
}
// GetTitle returns the title of the scene. If the Title field is empty,
// then the base filename is returned.
func (s Scene) GetTitle() string {
if s.Title.String != "" {
return s.Title.String
if s.Title != "" {
return s.Title
}
return filepath.Base(s.Path)
return filepath.Base(s.Path())
}
// GetHash returns the hash of the scene, based on the hash algorithm provided. If
// hash algorithm is MD5, then Checksum is returned. Otherwise, OSHash is returned.
func (s Scene) GetHash(hashAlgorithm HashAlgorithm) string {
return s.File().GetHash(hashAlgorithm)
}
func (s Scene) GetMinResolution() int64 {
if s.Width.Int64 < s.Height.Int64 {
return s.Width.Int64
f := s.PrimaryFile()
if f == nil {
return ""
}
return s.Height.Int64
switch hashAlgorithm {
case HashAlgorithmMd5:
return f.Base().Fingerprints.Get(file.FingerprintTypeMD5).(string)
case HashAlgorithmOshash:
return f.Base().Fingerprints.Get(file.FingerprintTypeOshash).(string)
}
return ""
}
func (s Scene) GetMinResolution() int {
f := s.PrimaryFile()
if f == nil {
return 0
}
w := f.Width
h := f.Height
if w < h {
return w
}
return h
}
// SceneFileType represents the file metadata for a scene.
@@ -246,12 +262,12 @@ func (s *Scenes) New() interface{} {
return &Scene{}
}
type SceneCaption struct {
type VideoCaption struct {
LanguageCode string `json:"language_code"`
Filename string `json:"filename"`
CaptionType string `json:"caption_type"`
}
func (c SceneCaption) Path(scenePath string) string {
return filepath.Join(filepath.Dir(scenePath), c.Filename)
func (c VideoCaption) Path(filePath string) string {
return filepath.Join(filepath.Dir(filePath), c.Filename)
}

View File

@@ -1,7 +1,6 @@
package models
import (
"database/sql"
"reflect"
"testing"
)
@@ -23,31 +22,25 @@ func TestScenePartial_UpdateInput(t *testing.T) {
studioIDStr = "2"
)
dateObj := NewDate(date)
tests := []struct {
name string
id int
s ScenePartial
want SceneUpdateInput
}{
{
"full",
id,
ScenePartial{
ID: id,
Title: NullStringPtr(title),
Details: NullStringPtr(details),
URL: NullStringPtr(url),
Date: &SQLiteDate{
String: date,
Valid: true,
},
Rating: &sql.NullInt64{
Int64: int64(rating),
Valid: true,
},
Organized: &organized,
StudioID: &sql.NullInt64{
Int64: int64(studioID),
Valid: true,
},
Title: NewOptionalString(title),
Details: NewOptionalString(details),
URL: NewOptionalString(url),
Date: NewOptionalDate(dateObj),
Rating: NewOptionalInt(rating),
Organized: NewOptionalBool(organized),
StudioID: NewOptionalInt(studioID),
},
SceneUpdateInput{
ID: idStr,
@@ -62,9 +55,8 @@ func TestScenePartial_UpdateInput(t *testing.T) {
},
{
"empty",
ScenePartial{
ID: id,
},
id,
ScenePartial{},
SceneUpdateInput{
ID: idStr,
},
@@ -72,7 +64,7 @@ func TestScenePartial_UpdateInput(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.s.UpdateInput(); !reflect.DeepEqual(got, tt.want) {
if got := tt.s.UpdateInput(tt.id); !reflect.DeepEqual(got, tt.want) {
t.Errorf("ScenePartial.UpdateInput() = %v, want %v", got, tt.want)
}
})

View File

@@ -154,7 +154,7 @@ type PerformerWriter interface {
Destroy(ctx context.Context, id int) error
UpdateImage(ctx context.Context, performerID int, image []byte) error
DestroyImage(ctx context.Context, performerID int) error
UpdateStashIDs(ctx context.Context, performerID int, stashIDs []StashID) error
UpdateStashIDs(ctx context.Context, performerID int, stashIDs []*StashID) error
UpdateTags(ctx context.Context, performerID int, tagIDs []int) error
}

View File

@@ -3,17 +3,21 @@ package models
import (
"context"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/txn"
)
type TxnManager interface {
txn.Manager
txn.DatabaseProvider
Reset() error
}
type Repository struct {
TxnManager
File file.Store
Folder file.FolderStore
Gallery GalleryReaderWriter
Image ImageReaderWriter
Movie MovieReaderWriter

View File

@@ -1,6 +1,10 @@
package models
import "context"
import (
"context"
"github.com/stashapp/stash/pkg/file"
)
type PHashDuplicationCriterionInput struct {
Duplicated *bool `json:"duplicated"`
@@ -121,9 +125,9 @@ type SceneReader interface {
SceneFinder
// TODO - remove this in another PR
Find(ctx context.Context, id int) (*Scene, error)
FindByChecksum(ctx context.Context, checksum string) (*Scene, error)
FindByOSHash(ctx context.Context, oshash string) (*Scene, error)
FindByPath(ctx context.Context, path string) (*Scene, error)
FindByChecksum(ctx context.Context, checksum string) ([]*Scene, error)
FindByOSHash(ctx context.Context, oshash string) ([]*Scene, error)
FindByPath(ctx context.Context, path string) ([]*Scene, error)
FindByPerformerID(ctx context.Context, performerID int) ([]*Scene, error)
FindByGalleryID(ctx context.Context, performerID int) ([]*Scene, error)
FindDuplicates(ctx context.Context, distance int) ([][]*Scene, error)
@@ -142,32 +146,19 @@ type SceneReader interface {
Wall(ctx context.Context, q *string) ([]*Scene, error)
All(ctx context.Context) ([]*Scene, error)
Query(ctx context.Context, options SceneQueryOptions) (*SceneQueryResult, error)
GetCaptions(ctx context.Context, sceneID int) ([]*SceneCaption, error)
GetCover(ctx context.Context, sceneID int) ([]byte, error)
GetMovies(ctx context.Context, sceneID int) ([]MoviesScenes, error)
GetTagIDs(ctx context.Context, sceneID int) ([]int, error)
GetGalleryIDs(ctx context.Context, sceneID int) ([]int, error)
GetPerformerIDs(ctx context.Context, sceneID int) ([]int, error)
GetStashIDs(ctx context.Context, sceneID int) ([]*StashID, error)
}
type SceneWriter interface {
Create(ctx context.Context, newScene Scene) (*Scene, error)
Update(ctx context.Context, updatedScene ScenePartial) (*Scene, error)
UpdateFull(ctx context.Context, updatedScene Scene) (*Scene, error)
Create(ctx context.Context, newScene *Scene, fileIDs []file.ID) error
Update(ctx context.Context, updatedScene *Scene) error
UpdatePartial(ctx context.Context, id int, updatedScene ScenePartial) (*Scene, error)
IncrementOCounter(ctx context.Context, id int) (int, error)
DecrementOCounter(ctx context.Context, id int) (int, error)
ResetOCounter(ctx context.Context, id int) (int, error)
UpdateFileModTime(ctx context.Context, id int, modTime NullSQLiteTimestamp) error
Destroy(ctx context.Context, id int) error
UpdateCaptions(ctx context.Context, id int, captions []*SceneCaption) error
UpdateCover(ctx context.Context, sceneID int, cover []byte) error
DestroyCover(ctx context.Context, sceneID int) error
UpdatePerformers(ctx context.Context, sceneID int, performerIDs []int) error
UpdateTags(ctx context.Context, sceneID int, tagIDs []int) error
UpdateGalleries(ctx context.Context, sceneID int, galleryIDs []int) error
UpdateMovies(ctx context.Context, sceneID int, movies []MoviesScenes) error
UpdateStashIDs(ctx context.Context, sceneID int, stashIDs []StashID) error
}
type SceneReaderWriter interface {

View File

@@ -2,7 +2,6 @@ package models
import (
"database/sql"
"strconv"
)
func NullString(v string) sql.NullString {
@@ -12,43 +11,9 @@ func NullString(v string) sql.NullString {
}
}
func NullStringPtr(v string) *sql.NullString {
return &sql.NullString{
String: v,
Valid: true,
}
}
func NullInt64(v int64) sql.NullInt64 {
return sql.NullInt64{
Int64: v,
Valid: true,
}
}
func nullStringPtrToStringPtr(v *sql.NullString) *string {
if v == nil || !v.Valid {
return nil
}
vv := v.String
return &vv
}
func nullInt64PtrToIntPtr(v *sql.NullInt64) *int {
if v == nil || !v.Valid {
return nil
}
vv := int(v.Int64)
return &vv
}
func nullInt64PtrToStringPtr(v *sql.NullInt64) *string {
if v == nil || !v.Valid {
return nil
}
vv := strconv.FormatInt(v.Int64, 10)
return &vv
}

View File

@@ -9,11 +9,14 @@ import (
"github.com/stashapp/stash/pkg/utils"
)
// TODO - this should be moved to sqlite
type SQLiteDate struct {
String string
Valid bool
}
const sqliteDateLayout = "2006-01-02"
// Scan implements the Scanner interface.
func (t *SQLiteDate) Scan(value interface{}) error {
dateTime, ok := value.(time.Time)
@@ -23,7 +26,7 @@ func (t *SQLiteDate) Scan(value interface{}) error {
return nil
}
t.String = dateTime.Format("2006-01-02")
t.String = dateTime.Format(sqliteDateLayout)
if t.String != "" && t.String != "0001-01-01" {
t.Valid = true
} else {
@@ -44,7 +47,7 @@ func (t SQLiteDate) Value() (driver.Value, error) {
return "", nil
}
result, err := utils.ParseDateStringAsFormat(s, "2006-01-02")
result, err := utils.ParseDateStringAsFormat(s, sqliteDateLayout)
if err != nil {
return nil, fmt.Errorf("converting sqlite date %q: %w", s, err)
}
@@ -59,3 +62,21 @@ func (t *SQLiteDate) StringPtr() *string {
vv := t.String
return &vv
}
func (t *SQLiteDate) TimePtr() *time.Time {
if t == nil || !t.Valid {
return nil
}
ret, _ := time.Parse(sqliteDateLayout, t.String)
return &ret
}
func (t *SQLiteDate) DatePtr() *Date {
if t == nil || !t.Valid {
return nil
}
ret := NewDate(t.String)
return &ret
}

View File

@@ -1,19 +1,11 @@
package models
type StashIDInput struct {
Endpoint string `json:"endpoint"`
StashID string `json:"stash_id"`
type StashID struct {
StashID string `db:"stash_id" json:"stash_id"`
Endpoint string `db:"endpoint" json:"endpoint"`
}
func StashIDsFromInput(i []*StashIDInput) []StashID {
var ret []StashID
for _, stashID := range i {
newJoin := StashID{
StashID: stashID.StashID,
Endpoint: stashID.Endpoint,
}
ret = append(ret, newJoin)
}
return ret
type UpdateStashIDs struct {
StashIDs []StashID `json:"stash_ids"`
Mode RelationshipUpdateMode `json:"mode"`
}

View File

@@ -55,7 +55,7 @@ type StudioWriter interface {
Destroy(ctx context.Context, id int) error
UpdateImage(ctx context.Context, studioID int, image []byte) error
DestroyImage(ctx context.Context, studioID int) error
UpdateStashIDs(ctx context.Context, studioID int, stashIDs []StashID) error
UpdateStashIDs(ctx context.Context, studioID int, stashIDs []*StashID) error
UpdateAliases(ctx context.Context, studioID int, aliases []string) error
}

65
pkg/models/update.go Normal file
View File

@@ -0,0 +1,65 @@
package models
import (
"fmt"
"io"
"strconv"
"github.com/stashapp/stash/pkg/sliceutil/intslice"
)
type RelationshipUpdateMode string
const (
RelationshipUpdateModeSet RelationshipUpdateMode = "SET"
RelationshipUpdateModeAdd RelationshipUpdateMode = "ADD"
RelationshipUpdateModeRemove RelationshipUpdateMode = "REMOVE"
)
var AllRelationshipUpdateMode = []RelationshipUpdateMode{
RelationshipUpdateModeSet,
RelationshipUpdateModeAdd,
RelationshipUpdateModeRemove,
}
func (e RelationshipUpdateMode) IsValid() bool {
switch e {
case RelationshipUpdateModeSet, RelationshipUpdateModeAdd, RelationshipUpdateModeRemove:
return true
}
return false
}
func (e RelationshipUpdateMode) String() string {
return string(e)
}
func (e *RelationshipUpdateMode) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = RelationshipUpdateMode(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid RelationshipUpdateMode", str)
}
return nil
}
func (e RelationshipUpdateMode) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
type UpdateIDs struct {
IDs []int `json:"ids"`
Mode RelationshipUpdateMode `json:"mode"`
}
func (u *UpdateIDs) IDStrings() []string {
if u == nil {
return nil
}
return intslice.IntSliceToStringSlice(u.IDs)
}

249
pkg/models/value.go Normal file
View File

@@ -0,0 +1,249 @@
package models
import (
"strconv"
"time"
)
// OptionalString represents an optional string argument that may be null.
// A value is only considered null if both Set and Null is true.
type OptionalString struct {
Value string
Null bool
Set bool
}
// Ptr returns a pointer to the underlying value. Returns nil if Set is false or Null is true.
func (o *OptionalString) Ptr() *string {
if !o.Set || o.Null {
return nil
}
v := o.Value
return &v
}
// NewOptionalString returns a new OptionalString with the given value.
func NewOptionalString(v string) OptionalString {
return OptionalString{v, false, true}
}
// NewOptionalStringPtr returns a new OptionalString with the given value.
// If the value is nil, the returned OptionalString will be set and null.
func NewOptionalStringPtr(v *string) OptionalString {
if v == nil {
return OptionalString{
Null: true,
Set: true,
}
}
return OptionalString{*v, false, true}
}
// OptionalInt represents an optional int argument that may be null. See OptionalString.
type OptionalInt struct {
Value int
Null bool
Set bool
}
// Ptr returns a pointer to the underlying value. Returns nil if Set is false or Null is true.
func (o *OptionalInt) Ptr() *int {
if !o.Set || o.Null {
return nil
}
v := o.Value
return &v
}
// NewOptionalInt returns a new OptionalInt with the given value.
func NewOptionalInt(v int) OptionalInt {
return OptionalInt{v, false, true}
}
// NewOptionalIntPtr returns a new OptionalInt with the given value.
// If the value is nil, the returned OptionalInt will be set and null.
func NewOptionalIntPtr(v *int) OptionalInt {
if v == nil {
return OptionalInt{
Null: true,
Set: true,
}
}
return OptionalInt{*v, false, true}
}
// StringPtr returns a pointer to a string representation of the value.
// Returns nil if Set is false or null is true.
func (o *OptionalInt) StringPtr() *string {
if !o.Set || o.Null {
return nil
}
v := strconv.Itoa(o.Value)
return &v
}
// OptionalInt64 represents an optional int64 argument that may be null. See OptionalString.
type OptionalInt64 struct {
Value int64
Null bool
Set bool
}
// Ptr returns a pointer to the underlying value. Returns nil if Set is false or Null is true.
func (o *OptionalInt64) Ptr() *int64 {
if !o.Set || o.Null {
return nil
}
v := o.Value
return &v
}
// NewOptionalInt64 returns a new OptionalInt64 with the given value.
func NewOptionalInt64(v int64) OptionalInt64 {
return OptionalInt64{v, false, true}
}
// NewOptionalInt64Ptr returns a new OptionalInt64 with the given value.
// If the value is nil, the returned OptionalInt64 will be set and null.
func NewOptionalInt64Ptr(v *int64) OptionalInt64 {
if v == nil {
return OptionalInt64{
Null: true,
Set: true,
}
}
return OptionalInt64{*v, false, true}
}
// OptionalBool represents an optional int64 argument that may be null. See OptionalString.
type OptionalBool struct {
Value bool
Null bool
Set bool
}
func (o *OptionalBool) Ptr() *bool {
if !o.Set || o.Null {
return nil
}
v := o.Value
return &v
}
// NewOptionalBool returns a new OptionalBool with the given value.
func NewOptionalBool(v bool) OptionalBool {
return OptionalBool{v, false, true}
}
// NewOptionalBoolPtr returns a new OptionalBool with the given value.
// If the value is nil, the returned OptionalBool will be set and null.
func NewOptionalBoolPtr(v *bool) OptionalBool {
if v == nil {
return OptionalBool{
Null: true,
Set: true,
}
}
return OptionalBool{*v, false, true}
}
// OptionalBool represents an optional float64 argument that may be null. See OptionalString.
type OptionalFloat64 struct {
Value float64
Null bool
Set bool
}
// Ptr returns a pointer to the underlying value. Returns nil if Set is false or Null is true.
func (o *OptionalFloat64) Ptr() *float64 {
if !o.Set || o.Null {
return nil
}
v := o.Value
return &v
}
// NewOptionalFloat64 returns a new OptionalFloat64 with the given value.
func NewOptionalFloat64(v float64) OptionalFloat64 {
return OptionalFloat64{v, false, true}
}
// OptionalDate represents an optional date argument that may be null. See OptionalString.
type OptionalDate struct {
Value Date
Null bool
Set bool
}
// Ptr returns a pointer to the underlying value. Returns nil if Set is false or Null is true.
func (o *OptionalDate) Ptr() *Date {
if !o.Set || o.Null {
return nil
}
v := o.Value
return &v
}
// NewOptionalDate returns a new OptionalDate with the given value.
func NewOptionalDate(v Date) OptionalDate {
return OptionalDate{v, false, true}
}
// NewOptionalBoolPtr returns a new OptionalDate with the given value.
// If the value is nil, the returned OptionalDate will be set and null.
func NewOptionalDatePtr(v *Date) OptionalDate {
if v == nil {
return OptionalDate{
Null: true,
Set: true,
}
}
return OptionalDate{*v, false, true}
}
// OptionalTime represents an optional time argument that may be null. See OptionalString.
type OptionalTime struct {
Value time.Time
Null bool
Set bool
}
// NewOptionalTime returns a new OptionalTime with the given value.
func NewOptionalTime(v time.Time) OptionalTime {
return OptionalTime{v, false, true}
}
// NewOptionalTimePtr returns a new OptionalTime with the given value.
// If the value is nil, the returned OptionalTime will be set and null.
func NewOptionalTimePtr(v *time.Time) OptionalTime {
if v == nil {
return OptionalTime{
Null: true,
Set: true,
}
}
return OptionalTime{*v, false, true}
}
// Ptr returns a pointer to the underlying value. Returns nil if Set is false or Null is true.
func (o *OptionalTime) Ptr() *time.Time {
if !o.Set || o.Null {
return nil
}
v := o.Value
return &v
}