Model refactor (#3915)

* Add mockery config file
* Move basic file/folder structs to models
* Fix hack due to import loop
* Move file interfaces to models
* Move folder interfaces to models
* Move scene interfaces to models
* Move scene marker interfaces to models
* Move image interfaces to models
* Move gallery interfaces to models
* Move gallery chapter interfaces to models
* Move studio interfaces to models
* Move movie interfaces to models
* Move performer interfaces to models
* Move tag interfaces to models
* Move autotag interfaces to models
* Regenerate mocks
This commit is contained in:
DingDongSoLong4 2023-09-01 02:39:29 +02:00 committed by GitHub
parent 20520a58b4
commit c364346a59
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
185 changed files with 3840 additions and 2559 deletions

4
.mockery.yml Normal file
View file

@ -0,0 +1,4 @@
dir: ./pkg/models
name: ".*ReaderWriter"
outpkg: mocks
output: ./pkg/models/mocks

View file

@ -319,7 +319,7 @@ it:
# generates test mocks
.PHONY: generate-test-mocks
generate-test-mocks:
go run github.com/vektra/mockery/v2 --dir ./pkg/models --name '.*ReaderWriter' --outpkg mocks --output ./pkg/models/mocks
go run github.com/vektra/mockery/v2
# runs server
# sets the config file to use the local dev config

View file

@ -8,8 +8,8 @@ import (
flag "github.com/spf13/pflag"
"github.com/stashapp/stash/pkg/ffmpeg"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/hash/videophash"
"github.com/stashapp/stash/pkg/models"
)
func customUsage() {
@ -28,8 +28,8 @@ func printPhash(ff *ffmpeg.FFMpeg, ffp ffmpeg.FFProbe, inputfile string, quiet *
// videoFile.Path (from BaseFile)
// videoFile.Duration
// The rest of the struct isn't needed.
vf := &file.VideoFile{
BaseFile: &file.BaseFile{Path: inputfile},
vf := &models.VideoFile{
BaseFile: &models.BaseFile{Path: inputfile},
Duration: ffvideoFile.FileDuration,
}

View file

@ -23,6 +23,12 @@ autobind:
models:
# Scalars
ID:
model:
- github.com/99designs/gqlgen/graphql.ID
- github.com/99designs/gqlgen/graphql.IntID
- github.com/stashapp/stash/pkg/models.FileID
- github.com/stashapp/stash/pkg/models.FolderID
Int64:
model: github.com/99designs/gqlgen/graphql.Int64
Timestamp:
@ -33,6 +39,30 @@ models:
fields:
title:
resolver: true
# override models, from internal/api/models.go
BaseFile:
model: github.com/stashapp/stash/internal/api.BaseFile
GalleryFile:
model: github.com/stashapp/stash/internal/api.GalleryFile
fields:
# override fingerprint field
fingerprints:
fieldName: FingerprintSlice
VideoFile:
fields:
# override fingerprint field
fingerprints:
fieldName: FingerprintSlice
# override float fields - #1572
duration:
fieldName: DurationFinite
frame_rate:
fieldName: FrameRateFinite
ImageFile:
fields:
# override fingerprint field
fingerprints:
fieldName: FingerprintSlice
# autobind on config causes generation issues
BlobsStorageType:
model: github.com/stashapp/stash/internal/manager/config.BlobsStorageType

View file

@ -1,14 +1,14 @@
//go:generate go run -mod=vendor github.com/vektah/dataloaden SceneLoader int *github.com/stashapp/stash/pkg/models.Scene
//go:generate go run -mod=vendor github.com/vektah/dataloaden GalleryLoader int *github.com/stashapp/stash/pkg/models.Gallery
//go:generate go run -mod=vendor github.com/vektah/dataloaden ImageLoader int *github.com/stashapp/stash/pkg/models.Image
//go:generate go run -mod=vendor github.com/vektah/dataloaden PerformerLoader int *github.com/stashapp/stash/pkg/models.Performer
//go:generate go run -mod=vendor github.com/vektah/dataloaden StudioLoader int *github.com/stashapp/stash/pkg/models.Studio
//go:generate go run -mod=vendor github.com/vektah/dataloaden TagLoader int *github.com/stashapp/stash/pkg/models.Tag
//go:generate go run -mod=vendor github.com/vektah/dataloaden MovieLoader int *github.com/stashapp/stash/pkg/models.Movie
//go:generate go run -mod=vendor github.com/vektah/dataloaden FileLoader github.com/stashapp/stash/pkg/file.ID github.com/stashapp/stash/pkg/file.File
//go:generate go run -mod=vendor github.com/vektah/dataloaden SceneFileIDsLoader int []github.com/stashapp/stash/pkg/file.ID
//go:generate go run -mod=vendor github.com/vektah/dataloaden ImageFileIDsLoader int []github.com/stashapp/stash/pkg/file.ID
//go:generate go run -mod=vendor github.com/vektah/dataloaden GalleryFileIDsLoader int []github.com/stashapp/stash/pkg/file.ID
//go:generate go run github.com/vektah/dataloaden SceneLoader int *github.com/stashapp/stash/pkg/models.Scene
//go:generate go run github.com/vektah/dataloaden GalleryLoader int *github.com/stashapp/stash/pkg/models.Gallery
//go:generate go run github.com/vektah/dataloaden ImageLoader int *github.com/stashapp/stash/pkg/models.Image
//go:generate go run github.com/vektah/dataloaden PerformerLoader int *github.com/stashapp/stash/pkg/models.Performer
//go:generate go run github.com/vektah/dataloaden StudioLoader int *github.com/stashapp/stash/pkg/models.Studio
//go:generate go run github.com/vektah/dataloaden TagLoader int *github.com/stashapp/stash/pkg/models.Tag
//go:generate go run github.com/vektah/dataloaden MovieLoader int *github.com/stashapp/stash/pkg/models.Movie
//go:generate go run github.com/vektah/dataloaden FileLoader github.com/stashapp/stash/pkg/models.FileID github.com/stashapp/stash/pkg/models.File
//go:generate go run github.com/vektah/dataloaden SceneFileIDsLoader int []github.com/stashapp/stash/pkg/models.FileID
//go:generate go run github.com/vektah/dataloaden ImageFileIDsLoader int []github.com/stashapp/stash/pkg/models.FileID
//go:generate go run github.com/vektah/dataloaden GalleryFileIDsLoader int []github.com/stashapp/stash/pkg/models.FileID
package loaders
@ -18,7 +18,6 @@ import (
"time"
"github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/txn"
)
@ -216,8 +215,8 @@ func (m Middleware) fetchMovies(ctx context.Context) func(keys []int) ([]*models
}
}
func (m Middleware) fetchFiles(ctx context.Context) func(keys []file.ID) ([]file.File, []error) {
return func(keys []file.ID) (ret []file.File, errs []error) {
func (m Middleware) fetchFiles(ctx context.Context) func(keys []models.FileID) ([]models.File, []error) {
return func(keys []models.FileID) (ret []models.File, errs []error) {
err := m.withTxn(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.File.Find(ctx, keys...)
@ -227,8 +226,8 @@ func (m Middleware) fetchFiles(ctx context.Context) func(keys []file.ID) ([]file
}
}
func (m Middleware) fetchScenesFileIDs(ctx context.Context) func(keys []int) ([][]file.ID, []error) {
return func(keys []int) (ret [][]file.ID, errs []error) {
func (m Middleware) fetchScenesFileIDs(ctx context.Context) func(keys []int) ([][]models.FileID, []error) {
return func(keys []int) (ret [][]models.FileID, errs []error) {
err := m.withTxn(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Scene.GetManyFileIDs(ctx, keys)
@ -238,8 +237,8 @@ func (m Middleware) fetchScenesFileIDs(ctx context.Context) func(keys []int) ([]
}
}
func (m Middleware) fetchImagesFileIDs(ctx context.Context) func(keys []int) ([][]file.ID, []error) {
return func(keys []int) (ret [][]file.ID, errs []error) {
func (m Middleware) fetchImagesFileIDs(ctx context.Context) func(keys []int) ([][]models.FileID, []error) {
return func(keys []int) (ret [][]models.FileID, errs []error) {
err := m.withTxn(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Image.GetManyFileIDs(ctx, keys)
@ -249,8 +248,8 @@ func (m Middleware) fetchImagesFileIDs(ctx context.Context) func(keys []int) ([]
}
}
func (m Middleware) fetchGalleriesFileIDs(ctx context.Context) func(keys []int) ([][]file.ID, []error) {
return func(keys []int) (ret [][]file.ID, errs []error) {
func (m Middleware) fetchGalleriesFileIDs(ctx context.Context) func(keys []int) ([][]models.FileID, []error) {
return func(keys []int) (ret [][]models.FileID, errs []error) {
err := m.withTxn(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Gallery.GetManyFileIDs(ctx, keys)

View file

@ -6,13 +6,13 @@ import (
"sync"
"time"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
)
// FileLoaderConfig captures the config to create a new FileLoader
type FileLoaderConfig struct {
// Fetch is a method that provides the data for the loader
Fetch func(keys []file.ID) ([]file.File, []error)
Fetch func(keys []models.FileID) ([]models.File, []error)
// Wait is how long wait before sending a batch
Wait time.Duration
@ -33,7 +33,7 @@ func NewFileLoader(config FileLoaderConfig) *FileLoader {
// FileLoader batches and caches requests
type FileLoader struct {
// this method provides the data for the loader
fetch func(keys []file.ID) ([]file.File, []error)
fetch func(keys []models.FileID) ([]models.File, []error)
// how long to done before sending a batch
wait time.Duration
@ -44,7 +44,7 @@ type FileLoader struct {
// INTERNAL
// lazily created cache
cache map[file.ID]file.File
cache map[models.FileID]models.File
// the current batch. keys will continue to be collected until timeout is hit,
// then everything will be sent to the fetch method and out to the listeners
@ -55,26 +55,26 @@ type FileLoader struct {
}
type fileLoaderBatch struct {
keys []file.ID
data []file.File
keys []models.FileID
data []models.File
error []error
closing bool
done chan struct{}
}
// Load a File by key, batching and caching will be applied automatically
func (l *FileLoader) Load(key file.ID) (file.File, error) {
func (l *FileLoader) Load(key models.FileID) (models.File, error) {
return l.LoadThunk(key)()
}
// LoadThunk returns a function that when called will block waiting for a File.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *FileLoader) LoadThunk(key file.ID) func() (file.File, error) {
func (l *FileLoader) LoadThunk(key models.FileID) func() (models.File, error) {
l.mu.Lock()
if it, ok := l.cache[key]; ok {
l.mu.Unlock()
return func() (file.File, error) {
return func() (models.File, error) {
return it, nil
}
}
@ -85,10 +85,10 @@ func (l *FileLoader) LoadThunk(key file.ID) func() (file.File, error) {
pos := batch.keyIndex(l, key)
l.mu.Unlock()
return func() (file.File, error) {
return func() (models.File, error) {
<-batch.done
var data file.File
var data models.File
if pos < len(batch.data) {
data = batch.data[pos]
}
@ -113,14 +113,14 @@ func (l *FileLoader) LoadThunk(key file.ID) func() (file.File, error) {
// LoadAll fetches many keys at once. It will be broken into appropriate sized
// sub batches depending on how the loader is configured
func (l *FileLoader) LoadAll(keys []file.ID) ([]file.File, []error) {
results := make([]func() (file.File, error), len(keys))
func (l *FileLoader) LoadAll(keys []models.FileID) ([]models.File, []error) {
results := make([]func() (models.File, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
files := make([]file.File, len(keys))
files := make([]models.File, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
files[i], errors[i] = thunk()
@ -131,13 +131,13 @@ func (l *FileLoader) LoadAll(keys []file.ID) ([]file.File, []error) {
// LoadAllThunk returns a function that when called will block waiting for a Files.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *FileLoader) LoadAllThunk(keys []file.ID) func() ([]file.File, []error) {
results := make([]func() (file.File, error), len(keys))
func (l *FileLoader) LoadAllThunk(keys []models.FileID) func() ([]models.File, []error) {
results := make([]func() (models.File, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
return func() ([]file.File, []error) {
files := make([]file.File, len(keys))
return func() ([]models.File, []error) {
files := make([]models.File, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
files[i], errors[i] = thunk()
@ -149,7 +149,7 @@ func (l *FileLoader) LoadAllThunk(keys []file.ID) func() ([]file.File, []error)
// Prime the cache with the provided key and value. If the key already exists, no change is made
// and false is returned.
// (To forcefully prime the cache, clear the key first with loader.clear(key).prime(key, value).)
func (l *FileLoader) Prime(key file.ID, value file.File) bool {
func (l *FileLoader) Prime(key models.FileID, value models.File) bool {
l.mu.Lock()
var found bool
if _, found = l.cache[key]; !found {
@ -160,22 +160,22 @@ func (l *FileLoader) Prime(key file.ID, value file.File) bool {
}
// Clear the value at key from the cache, if it exists
func (l *FileLoader) Clear(key file.ID) {
func (l *FileLoader) Clear(key models.FileID) {
l.mu.Lock()
delete(l.cache, key)
l.mu.Unlock()
}
func (l *FileLoader) unsafeSet(key file.ID, value file.File) {
func (l *FileLoader) unsafeSet(key models.FileID, value models.File) {
if l.cache == nil {
l.cache = map[file.ID]file.File{}
l.cache = map[models.FileID]models.File{}
}
l.cache[key] = value
}
// keyIndex will return the location of the key in the batch, if its not found
// it will add the key to the batch
func (b *fileLoaderBatch) keyIndex(l *FileLoader, key file.ID) int {
func (b *fileLoaderBatch) keyIndex(l *FileLoader, key models.FileID) int {
for i, existingKey := range b.keys {
if key == existingKey {
return i

View file

@ -6,13 +6,13 @@ import (
"sync"
"time"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
)
// GalleryFileIDsLoaderConfig captures the config to create a new GalleryFileIDsLoader
type GalleryFileIDsLoaderConfig struct {
// Fetch is a method that provides the data for the loader
Fetch func(keys []int) ([][]file.ID, []error)
Fetch func(keys []int) ([][]models.FileID, []error)
// Wait is how long wait before sending a batch
Wait time.Duration
@ -33,7 +33,7 @@ func NewGalleryFileIDsLoader(config GalleryFileIDsLoaderConfig) *GalleryFileIDsL
// GalleryFileIDsLoader batches and caches requests
type GalleryFileIDsLoader struct {
// this method provides the data for the loader
fetch func(keys []int) ([][]file.ID, []error)
fetch func(keys []int) ([][]models.FileID, []error)
// how long to done before sending a batch
wait time.Duration
@ -44,7 +44,7 @@ type GalleryFileIDsLoader struct {
// INTERNAL
// lazily created cache
cache map[int][]file.ID
cache map[int][]models.FileID
// the current batch. keys will continue to be collected until timeout is hit,
// then everything will be sent to the fetch method and out to the listeners
@ -56,25 +56,25 @@ type GalleryFileIDsLoader struct {
type galleryFileIDsLoaderBatch struct {
keys []int
data [][]file.ID
data [][]models.FileID
error []error
closing bool
done chan struct{}
}
// Load a ID by key, batching and caching will be applied automatically
func (l *GalleryFileIDsLoader) Load(key int) ([]file.ID, error) {
// Load a FileID by key, batching and caching will be applied automatically
func (l *GalleryFileIDsLoader) Load(key int) ([]models.FileID, error) {
return l.LoadThunk(key)()
}
// LoadThunk returns a function that when called will block waiting for a ID.
// LoadThunk returns a function that when called will block waiting for a FileID.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *GalleryFileIDsLoader) LoadThunk(key int) func() ([]file.ID, error) {
func (l *GalleryFileIDsLoader) LoadThunk(key int) func() ([]models.FileID, error) {
l.mu.Lock()
if it, ok := l.cache[key]; ok {
l.mu.Unlock()
return func() ([]file.ID, error) {
return func() ([]models.FileID, error) {
return it, nil
}
}
@ -85,10 +85,10 @@ func (l *GalleryFileIDsLoader) LoadThunk(key int) func() ([]file.ID, error) {
pos := batch.keyIndex(l, key)
l.mu.Unlock()
return func() ([]file.ID, error) {
return func() ([]models.FileID, error) {
<-batch.done
var data []file.ID
var data []models.FileID
if pos < len(batch.data) {
data = batch.data[pos]
}
@ -113,49 +113,49 @@ func (l *GalleryFileIDsLoader) LoadThunk(key int) func() ([]file.ID, error) {
// LoadAll fetches many keys at once. It will be broken into appropriate sized
// sub batches depending on how the loader is configured
func (l *GalleryFileIDsLoader) LoadAll(keys []int) ([][]file.ID, []error) {
results := make([]func() ([]file.ID, error), len(keys))
func (l *GalleryFileIDsLoader) LoadAll(keys []int) ([][]models.FileID, []error) {
results := make([]func() ([]models.FileID, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
iDs := make([][]file.ID, len(keys))
fileIDs := make([][]models.FileID, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
iDs[i], errors[i] = thunk()
fileIDs[i], errors[i] = thunk()
}
return iDs, errors
return fileIDs, errors
}
// LoadAllThunk returns a function that when called will block waiting for a IDs.
// LoadAllThunk returns a function that when called will block waiting for a FileIDs.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *GalleryFileIDsLoader) LoadAllThunk(keys []int) func() ([][]file.ID, []error) {
results := make([]func() ([]file.ID, error), len(keys))
func (l *GalleryFileIDsLoader) LoadAllThunk(keys []int) func() ([][]models.FileID, []error) {
results := make([]func() ([]models.FileID, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
return func() ([][]file.ID, []error) {
iDs := make([][]file.ID, len(keys))
return func() ([][]models.FileID, []error) {
fileIDs := make([][]models.FileID, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
iDs[i], errors[i] = thunk()
fileIDs[i], errors[i] = thunk()
}
return iDs, errors
return fileIDs, errors
}
}
// Prime the cache with the provided key and value. If the key already exists, no change is made
// and false is returned.
// (To forcefully prime the cache, clear the key first with loader.clear(key).prime(key, value).)
func (l *GalleryFileIDsLoader) Prime(key int, value []file.ID) bool {
func (l *GalleryFileIDsLoader) Prime(key int, value []models.FileID) bool {
l.mu.Lock()
var found bool
if _, found = l.cache[key]; !found {
// make a copy when writing to the cache, its easy to pass a pointer in from a loop var
// and end up with the whole cache pointing to the same value.
cpy := make([]file.ID, len(value))
cpy := make([]models.FileID, len(value))
copy(cpy, value)
l.unsafeSet(key, cpy)
}
@ -170,9 +170,9 @@ func (l *GalleryFileIDsLoader) Clear(key int) {
l.mu.Unlock()
}
func (l *GalleryFileIDsLoader) unsafeSet(key int, value []file.ID) {
func (l *GalleryFileIDsLoader) unsafeSet(key int, value []models.FileID) {
if l.cache == nil {
l.cache = map[int][]file.ID{}
l.cache = map[int][]models.FileID{}
}
l.cache[key] = value
}

View file

@ -6,13 +6,13 @@ import (
"sync"
"time"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
)
// ImageFileIDsLoaderConfig captures the config to create a new ImageFileIDsLoader
type ImageFileIDsLoaderConfig struct {
// Fetch is a method that provides the data for the loader
Fetch func(keys []int) ([][]file.ID, []error)
Fetch func(keys []int) ([][]models.FileID, []error)
// Wait is how long wait before sending a batch
Wait time.Duration
@ -33,7 +33,7 @@ func NewImageFileIDsLoader(config ImageFileIDsLoaderConfig) *ImageFileIDsLoader
// ImageFileIDsLoader batches and caches requests
type ImageFileIDsLoader struct {
// this method provides the data for the loader
fetch func(keys []int) ([][]file.ID, []error)
fetch func(keys []int) ([][]models.FileID, []error)
// how long to done before sending a batch
wait time.Duration
@ -44,7 +44,7 @@ type ImageFileIDsLoader struct {
// INTERNAL
// lazily created cache
cache map[int][]file.ID
cache map[int][]models.FileID
// the current batch. keys will continue to be collected until timeout is hit,
// then everything will be sent to the fetch method and out to the listeners
@ -56,25 +56,25 @@ type ImageFileIDsLoader struct {
type imageFileIDsLoaderBatch struct {
keys []int
data [][]file.ID
data [][]models.FileID
error []error
closing bool
done chan struct{}
}
// Load a ID by key, batching and caching will be applied automatically
func (l *ImageFileIDsLoader) Load(key int) ([]file.ID, error) {
// Load a FileID by key, batching and caching will be applied automatically
func (l *ImageFileIDsLoader) Load(key int) ([]models.FileID, error) {
return l.LoadThunk(key)()
}
// LoadThunk returns a function that when called will block waiting for a ID.
// LoadThunk returns a function that when called will block waiting for a FileID.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *ImageFileIDsLoader) LoadThunk(key int) func() ([]file.ID, error) {
func (l *ImageFileIDsLoader) LoadThunk(key int) func() ([]models.FileID, error) {
l.mu.Lock()
if it, ok := l.cache[key]; ok {
l.mu.Unlock()
return func() ([]file.ID, error) {
return func() ([]models.FileID, error) {
return it, nil
}
}
@ -85,10 +85,10 @@ func (l *ImageFileIDsLoader) LoadThunk(key int) func() ([]file.ID, error) {
pos := batch.keyIndex(l, key)
l.mu.Unlock()
return func() ([]file.ID, error) {
return func() ([]models.FileID, error) {
<-batch.done
var data []file.ID
var data []models.FileID
if pos < len(batch.data) {
data = batch.data[pos]
}
@ -113,49 +113,49 @@ func (l *ImageFileIDsLoader) LoadThunk(key int) func() ([]file.ID, error) {
// LoadAll fetches many keys at once. It will be broken into appropriate sized
// sub batches depending on how the loader is configured
func (l *ImageFileIDsLoader) LoadAll(keys []int) ([][]file.ID, []error) {
results := make([]func() ([]file.ID, error), len(keys))
func (l *ImageFileIDsLoader) LoadAll(keys []int) ([][]models.FileID, []error) {
results := make([]func() ([]models.FileID, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
iDs := make([][]file.ID, len(keys))
fileIDs := make([][]models.FileID, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
iDs[i], errors[i] = thunk()
fileIDs[i], errors[i] = thunk()
}
return iDs, errors
return fileIDs, errors
}
// LoadAllThunk returns a function that when called will block waiting for a IDs.
// LoadAllThunk returns a function that when called will block waiting for a FileIDs.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *ImageFileIDsLoader) LoadAllThunk(keys []int) func() ([][]file.ID, []error) {
results := make([]func() ([]file.ID, error), len(keys))
func (l *ImageFileIDsLoader) LoadAllThunk(keys []int) func() ([][]models.FileID, []error) {
results := make([]func() ([]models.FileID, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
return func() ([][]file.ID, []error) {
iDs := make([][]file.ID, len(keys))
return func() ([][]models.FileID, []error) {
fileIDs := make([][]models.FileID, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
iDs[i], errors[i] = thunk()
fileIDs[i], errors[i] = thunk()
}
return iDs, errors
return fileIDs, errors
}
}
// Prime the cache with the provided key and value. If the key already exists, no change is made
// and false is returned.
// (To forcefully prime the cache, clear the key first with loader.clear(key).prime(key, value).)
func (l *ImageFileIDsLoader) Prime(key int, value []file.ID) bool {
func (l *ImageFileIDsLoader) Prime(key int, value []models.FileID) bool {
l.mu.Lock()
var found bool
if _, found = l.cache[key]; !found {
// make a copy when writing to the cache, its easy to pass a pointer in from a loop var
// and end up with the whole cache pointing to the same value.
cpy := make([]file.ID, len(value))
cpy := make([]models.FileID, len(value))
copy(cpy, value)
l.unsafeSet(key, cpy)
}
@ -170,9 +170,9 @@ func (l *ImageFileIDsLoader) Clear(key int) {
l.mu.Unlock()
}
func (l *ImageFileIDsLoader) unsafeSet(key int, value []file.ID) {
func (l *ImageFileIDsLoader) unsafeSet(key int, value []models.FileID) {
if l.cache == nil {
l.cache = map[int][]file.ID{}
l.cache = map[int][]models.FileID{}
}
l.cache[key] = value
}

View file

@ -6,13 +6,13 @@ import (
"sync"
"time"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
)
// SceneFileIDsLoaderConfig captures the config to create a new SceneFileIDsLoader
type SceneFileIDsLoaderConfig struct {
// Fetch is a method that provides the data for the loader
Fetch func(keys []int) ([][]file.ID, []error)
Fetch func(keys []int) ([][]models.FileID, []error)
// Wait is how long wait before sending a batch
Wait time.Duration
@ -33,7 +33,7 @@ func NewSceneFileIDsLoader(config SceneFileIDsLoaderConfig) *SceneFileIDsLoader
// SceneFileIDsLoader batches and caches requests
type SceneFileIDsLoader struct {
// this method provides the data for the loader
fetch func(keys []int) ([][]file.ID, []error)
fetch func(keys []int) ([][]models.FileID, []error)
// how long to done before sending a batch
wait time.Duration
@ -44,7 +44,7 @@ type SceneFileIDsLoader struct {
// INTERNAL
// lazily created cache
cache map[int][]file.ID
cache map[int][]models.FileID
// the current batch. keys will continue to be collected until timeout is hit,
// then everything will be sent to the fetch method and out to the listeners
@ -56,25 +56,25 @@ type SceneFileIDsLoader struct {
type sceneFileIDsLoaderBatch struct {
keys []int
data [][]file.ID
data [][]models.FileID
error []error
closing bool
done chan struct{}
}
// Load a ID by key, batching and caching will be applied automatically
func (l *SceneFileIDsLoader) Load(key int) ([]file.ID, error) {
// Load a FileID by key, batching and caching will be applied automatically
func (l *SceneFileIDsLoader) Load(key int) ([]models.FileID, error) {
return l.LoadThunk(key)()
}
// LoadThunk returns a function that when called will block waiting for a ID.
// LoadThunk returns a function that when called will block waiting for a FileID.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *SceneFileIDsLoader) LoadThunk(key int) func() ([]file.ID, error) {
func (l *SceneFileIDsLoader) LoadThunk(key int) func() ([]models.FileID, error) {
l.mu.Lock()
if it, ok := l.cache[key]; ok {
l.mu.Unlock()
return func() ([]file.ID, error) {
return func() ([]models.FileID, error) {
return it, nil
}
}
@ -85,10 +85,10 @@ func (l *SceneFileIDsLoader) LoadThunk(key int) func() ([]file.ID, error) {
pos := batch.keyIndex(l, key)
l.mu.Unlock()
return func() ([]file.ID, error) {
return func() ([]models.FileID, error) {
<-batch.done
var data []file.ID
var data []models.FileID
if pos < len(batch.data) {
data = batch.data[pos]
}
@ -113,49 +113,49 @@ func (l *SceneFileIDsLoader) LoadThunk(key int) func() ([]file.ID, error) {
// LoadAll fetches many keys at once. It will be broken into appropriate sized
// sub batches depending on how the loader is configured
func (l *SceneFileIDsLoader) LoadAll(keys []int) ([][]file.ID, []error) {
results := make([]func() ([]file.ID, error), len(keys))
func (l *SceneFileIDsLoader) LoadAll(keys []int) ([][]models.FileID, []error) {
results := make([]func() ([]models.FileID, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
iDs := make([][]file.ID, len(keys))
fileIDs := make([][]models.FileID, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
iDs[i], errors[i] = thunk()
fileIDs[i], errors[i] = thunk()
}
return iDs, errors
return fileIDs, errors
}
// LoadAllThunk returns a function that when called will block waiting for a IDs.
// LoadAllThunk returns a function that when called will block waiting for a FileIDs.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *SceneFileIDsLoader) LoadAllThunk(keys []int) func() ([][]file.ID, []error) {
results := make([]func() ([]file.ID, error), len(keys))
func (l *SceneFileIDsLoader) LoadAllThunk(keys []int) func() ([][]models.FileID, []error) {
results := make([]func() ([]models.FileID, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
return func() ([][]file.ID, []error) {
iDs := make([][]file.ID, len(keys))
return func() ([][]models.FileID, []error) {
fileIDs := make([][]models.FileID, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
iDs[i], errors[i] = thunk()
fileIDs[i], errors[i] = thunk()
}
return iDs, errors
return fileIDs, errors
}
}
// Prime the cache with the provided key and value. If the key already exists, no change is made
// and false is returned.
// (To forcefully prime the cache, clear the key first with loader.clear(key).prime(key, value).)
func (l *SceneFileIDsLoader) Prime(key int, value []file.ID) bool {
func (l *SceneFileIDsLoader) Prime(key int, value []models.FileID) bool {
l.mu.Lock()
var found bool
if _, found = l.cache[key]; !found {
// make a copy when writing to the cache, its easy to pass a pointer in from a loop var
// and end up with the whole cache pointing to the same value.
cpy := make([]file.ID, len(value))
cpy := make([]models.FileID, len(value))
copy(cpy, value)
l.unsafeSet(key, cpy)
}
@ -170,9 +170,9 @@ func (l *SceneFileIDsLoader) Clear(key int) {
l.mu.Unlock()
}
func (l *SceneFileIDsLoader) unsafeSet(key int, value []file.ID) {
func (l *SceneFileIDsLoader) unsafeSet(key int, value []models.FileID) {
if l.cache == nil {
l.cache = map[int][]file.ID{}
l.cache = map[int][]models.FileID{}
}
l.cache[key] = value
}

View file

@ -9,9 +9,16 @@ import (
"github.com/99designs/gqlgen/graphql"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils"
)
type BaseFile interface{}
type GalleryFile struct {
*models.BaseFile
}
var ErrTimestamp = errors.New("cannot parse Timestamp")
func MarshalTimestamp(t time.Time) graphql.Marshaler {

View file

@ -2,18 +2,16 @@ package api
import (
"context"
"strconv"
"time"
"github.com/stashapp/stash/internal/api/loaders"
"github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/image"
"github.com/stashapp/stash/pkg/models"
)
func (r *galleryResolver) getPrimaryFile(ctx context.Context, obj *models.Gallery) (file.File, error) {
func (r *galleryResolver) getPrimaryFile(ctx context.Context, obj *models.Gallery) (models.File, error) {
if obj.PrimaryFileID != nil {
f, err := loaders.From(ctx).FileByID.Load(*obj.PrimaryFileID)
if err != nil {
@ -26,7 +24,7 @@ func (r *galleryResolver) getPrimaryFile(ctx context.Context, obj *models.Galler
return nil, nil
}
func (r *galleryResolver) getFiles(ctx context.Context, obj *models.Gallery) ([]file.File, error) {
func (r *galleryResolver) getFiles(ctx context.Context, obj *models.Gallery) ([]models.File, error) {
fileIDs, err := loaders.From(ctx).GalleryFiles.Load(obj.ID)
if err != nil {
return nil, err
@ -45,34 +43,20 @@ func (r *galleryResolver) Files(ctx context.Context, obj *models.Gallery) ([]*Ga
ret := make([]*GalleryFile, len(files))
for i, f := range files {
base := f.Base()
ret[i] = &GalleryFile{
ID: strconv.Itoa(int(base.ID)),
Path: base.Path,
Basename: base.Basename,
ParentFolderID: strconv.Itoa(int(base.ParentFolderID)),
ModTime: base.ModTime,
Size: base.Size,
CreatedAt: base.CreatedAt,
UpdatedAt: base.UpdatedAt,
Fingerprints: resolveFingerprints(base),
}
if base.ZipFileID != nil {
zipFileID := strconv.Itoa(int(*base.ZipFileID))
ret[i].ZipFileID = &zipFileID
BaseFile: f.Base(),
}
}
return ret, nil
}
func (r *galleryResolver) Folder(ctx context.Context, obj *models.Gallery) (*Folder, error) {
func (r *galleryResolver) Folder(ctx context.Context, obj *models.Gallery) (*models.Folder, error) {
if obj.FolderID == nil {
return nil, nil
}
var ret *file.Folder
var ret *models.Folder
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
var err error
@ -91,25 +75,7 @@ func (r *galleryResolver) Folder(ctx context.Context, obj *models.Gallery) (*Fol
return nil, nil
}
rr := &Folder{
ID: ret.ID.String(),
Path: ret.Path,
ModTime: ret.ModTime,
CreatedAt: ret.CreatedAt,
UpdatedAt: ret.UpdatedAt,
}
if ret.ParentFolderID != nil {
pfidStr := ret.ParentFolderID.String()
rr.ParentFolderID = &pfidStr
}
if ret.ZipFileID != nil {
zfidStr := ret.ZipFileID.String()
rr.ZipFileID = &zfidStr
}
return rr, nil
return ret, nil
}
func (r *galleryResolver) FileModTime(ctx context.Context, obj *models.Gallery) (*time.Time, error) {

View file

@ -3,57 +3,35 @@ package api
import (
"context"
"fmt"
"strconv"
"time"
"github.com/stashapp/stash/internal/api/loaders"
"github.com/stashapp/stash/internal/api/urlbuilders"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
)
func convertImageFile(f *file.ImageFile) *ImageFile {
ret := &ImageFile{
ID: strconv.Itoa(int(f.ID)),
Path: f.Path,
Basename: f.Basename,
ParentFolderID: strconv.Itoa(int(f.ParentFolderID)),
ModTime: f.ModTime,
Size: f.Size,
Width: f.Width,
Height: f.Height,
CreatedAt: f.CreatedAt,
UpdatedAt: f.UpdatedAt,
Fingerprints: resolveFingerprints(f.Base()),
func convertVisualFile(f models.File) (models.VisualFile, error) {
vf, ok := f.(models.VisualFile)
if !ok {
return nil, fmt.Errorf("file %s is not a visual file", f.Base().Path)
}
if f.ZipFileID != nil {
zipFileID := strconv.Itoa(int(*f.ZipFileID))
ret.ZipFileID = &zipFileID
}
return ret
return vf, nil
}
func (r *imageResolver) getPrimaryFile(ctx context.Context, obj *models.Image) (file.VisualFile, error) {
func (r *imageResolver) getPrimaryFile(ctx context.Context, obj *models.Image) (models.VisualFile, error) {
if obj.PrimaryFileID != nil {
f, err := loaders.From(ctx).FileByID.Load(*obj.PrimaryFileID)
if err != nil {
return nil, err
}
asFrame, ok := f.(file.VisualFile)
if !ok {
return nil, fmt.Errorf("file %T is not an frame", f)
}
return asFrame, nil
return convertVisualFile(f)
}
return nil, nil
}
func (r *imageResolver) getFiles(ctx context.Context, obj *models.Image) ([]file.File, error) {
func (r *imageResolver) getFiles(ctx context.Context, obj *models.Image) ([]models.File, error) {
fileIDs, err := loaders.From(ctx).ImageFiles.Load(obj.ID)
if err != nil {
return nil, err
@ -88,30 +66,21 @@ func (r *imageResolver) File(ctx context.Context, obj *models.Image) (*ImageFile
}, nil
}
func convertVisualFile(f file.File) VisualFile {
switch f := f.(type) {
case *file.ImageFile:
return convertImageFile(f)
case *file.VideoFile:
return convertVideoFile(f)
default:
panic(fmt.Sprintf("unknown file type %T", f))
}
}
func (r *imageResolver) VisualFiles(ctx context.Context, obj *models.Image) ([]VisualFile, error) {
fileIDs, err := loaders.From(ctx).ImageFiles.Load(obj.ID)
func (r *imageResolver) VisualFiles(ctx context.Context, obj *models.Image) ([]models.VisualFile, error) {
files, err := r.getFiles(ctx, obj)
if err != nil {
return nil, err
}
files, errs := loaders.From(ctx).FileByID.LoadAll(fileIDs)
ret := make([]VisualFile, len(files))
ret := make([]models.VisualFile, len(files))
for i, f := range files {
ret[i] = convertVisualFile(f)
ret[i], err = convertVisualFile(f)
if err != nil {
return nil, err
}
}
return ret, firstError(errs)
return ret, nil
}
func (r *imageResolver) Date(ctx context.Context, obj *models.Image) (*string, error) {
@ -122,24 +91,22 @@ func (r *imageResolver) Date(ctx context.Context, obj *models.Image) (*string, e
return nil, nil
}
func (r *imageResolver) Files(ctx context.Context, obj *models.Image) ([]*ImageFile, error) {
func (r *imageResolver) Files(ctx context.Context, obj *models.Image) ([]*models.ImageFile, error) {
files, err := r.getFiles(ctx, obj)
if err != nil {
return nil, err
}
var ret []*ImageFile
var ret []*models.ImageFile
for _, f := range files {
// filter out non-image files
imageFile, ok := f.(*file.ImageFile)
imageFile, ok := f.(*models.ImageFile)
if !ok {
continue
}
thisFile := convertImageFile(imageFile)
ret = append(ret, thisFile)
ret = append(ret, imageFile)
}
return ret, nil

View file

@ -9,50 +9,28 @@ import (
"github.com/stashapp/stash/internal/api/loaders"
"github.com/stashapp/stash/internal/api/urlbuilders"
"github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils"
)
func convertVideoFile(f *file.VideoFile) *VideoFile {
ret := &VideoFile{
ID: strconv.Itoa(int(f.ID)),
Path: f.Path,
Basename: f.Basename,
ParentFolderID: strconv.Itoa(int(f.ParentFolderID)),
ModTime: f.ModTime,
Format: f.Format,
Size: f.Size,
Duration: handleFloat64Value(f.Duration),
VideoCodec: f.VideoCodec,
AudioCodec: f.AudioCodec,
Width: f.Width,
Height: f.Height,
FrameRate: handleFloat64Value(f.FrameRate),
BitRate: int(f.BitRate),
CreatedAt: f.CreatedAt,
UpdatedAt: f.UpdatedAt,
Fingerprints: resolveFingerprints(f.Base()),
func convertVideoFile(f models.File) (*models.VideoFile, error) {
vf, ok := f.(*models.VideoFile)
if !ok {
return nil, fmt.Errorf("file %T is not a video file", f)
}
if f.ZipFileID != nil {
zipFileID := strconv.Itoa(int(*f.ZipFileID))
ret.ZipFileID = &zipFileID
}
return ret
return vf, nil
}
func (r *sceneResolver) getPrimaryFile(ctx context.Context, obj *models.Scene) (*file.VideoFile, error) {
func (r *sceneResolver) getPrimaryFile(ctx context.Context, obj *models.Scene) (*models.VideoFile, error) {
if obj.PrimaryFileID != nil {
f, err := loaders.From(ctx).FileByID.Load(*obj.PrimaryFileID)
if err != nil {
return nil, err
}
ret, ok := f.(*file.VideoFile)
if !ok {
return nil, fmt.Errorf("file %T is not an image file", f)
ret, err := convertVideoFile(f)
if err != nil {
return nil, err
}
obj.Files.SetPrimary(ret)
@ -65,26 +43,29 @@ func (r *sceneResolver) getPrimaryFile(ctx context.Context, obj *models.Scene) (
return nil, nil
}
func (r *sceneResolver) getFiles(ctx context.Context, obj *models.Scene) ([]*file.VideoFile, error) {
func (r *sceneResolver) getFiles(ctx context.Context, obj *models.Scene) ([]*models.VideoFile, error) {
fileIDs, err := loaders.From(ctx).SceneFiles.Load(obj.ID)
if err != nil {
return nil, err
}
files, errs := loaders.From(ctx).FileByID.LoadAll(fileIDs)
ret := make([]*file.VideoFile, len(files))
for i, bf := range files {
f, ok := bf.(*file.VideoFile)
if !ok {
return nil, fmt.Errorf("file %T is not a video file", f)
err = firstError(errs)
if err != nil {
return nil, err
}
ret[i] = f
ret := make([]*models.VideoFile, len(files))
for i, f := range files {
ret[i], err = convertVideoFile(f)
if err != nil {
return nil, err
}
}
obj.Files.Set(ret)
return ret, firstError(errs)
return ret, nil
}
func (r *sceneResolver) FileModTime(ctx context.Context, obj *models.Scene) (*time.Time, error) {
@ -132,19 +113,13 @@ func (r *sceneResolver) File(ctx context.Context, obj *models.Scene) (*models.Sc
}, nil
}
func (r *sceneResolver) Files(ctx context.Context, obj *models.Scene) ([]*VideoFile, error) {
func (r *sceneResolver) Files(ctx context.Context, obj *models.Scene) ([]*models.VideoFile, error) {
files, err := r.getFiles(ctx, obj)
if err != nil {
return nil, err
}
ret := make([]*VideoFile, len(files))
for i, f := range files {
ret[i] = convertVideoFile(f)
}
return ret, nil
return files, nil
}
func (r *sceneResolver) Rating(ctx context.Context, obj *models.Scene) (*int, error) {
@ -159,28 +134,6 @@ func (r *sceneResolver) Rating100(ctx context.Context, obj *models.Scene) (*int,
return obj.Rating, nil
}
func resolveFingerprints(f *file.BaseFile) []*Fingerprint {
ret := make([]*Fingerprint, len(f.Fingerprints))
for i, fp := range f.Fingerprints {
ret[i] = &Fingerprint{
Type: fp.Type,
Value: formatFingerprint(fp.Fingerprint),
}
}
return ret
}
func formatFingerprint(fp interface{}) string {
switch v := fp.(type) {
case int64:
return strconv.FormatUint(uint64(v), 16)
default:
return fmt.Sprintf("%v", fp)
}
}
func (r *sceneResolver) Paths(ctx context.Context, obj *models.Scene) (*ScenePathsType, error) {
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
config := manager.GetInstance().Config
@ -352,7 +305,7 @@ func (r *sceneResolver) Phash(ctx context.Context, obj *models.Scene) (*string,
return nil, nil
}
val := f.Fingerprints.Get(file.FingerprintTypePhash)
val := f.Fingerprints.Get(models.FingerprintTypePhash)
if val == nil {
return nil, nil
}

View file

@ -8,6 +8,7 @@ import (
"github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
)
@ -19,7 +20,7 @@ func (r *mutationResolver) MoveFiles(ctx context.Context, input MoveFilesInput)
mover.RegisterHooks(ctx, r.txnManager)
var (
folder *file.Folder
folder *models.Folder
basename string
)
@ -37,7 +38,7 @@ func (r *mutationResolver) MoveFiles(ctx context.Context, input MoveFilesInput)
return fmt.Errorf("invalid folder id %s: %w", *input.DestinationFolderID, err)
}
folder, err = folderStore.Find(ctx, file.FolderID(folderID))
folder, err = folderStore.Find(ctx, models.FolderID(folderID))
if err != nil {
return fmt.Errorf("finding destination folder: %w", err)
}
@ -82,7 +83,7 @@ func (r *mutationResolver) MoveFiles(ctx context.Context, input MoveFilesInput)
}
for _, fileIDInt := range fileIDs {
fileID := file.ID(fileIDInt)
fileID := models.FileID(fileIDInt)
f, err := fileStore.Find(ctx, fileID)
if err != nil {
return fmt.Errorf("finding file %d: %w", fileID, err)
@ -158,7 +159,7 @@ func (r *mutationResolver) DeleteFiles(ctx context.Context, ids []string) (ret b
qb := r.repository.File
for _, fileIDInt := range fileIDs {
fileID := file.ID(fileIDInt)
fileID := models.FileID(fileIDInt)
f, err := qb.Find(ctx, fileID)
if err != nil {
return err

View file

@ -199,7 +199,7 @@ func (r *mutationResolver) galleryUpdate(ctx context.Context, input models.Galle
return nil, fmt.Errorf("converting primary file id: %w", err)
}
converted := file.ID(primaryFileID)
converted := models.FileID(primaryFileID)
updatedGallery.PrimaryFileID = &converted
if err := originalGallery.LoadFiles(ctx, r.repository.Gallery); err != nil {
@ -207,7 +207,7 @@ func (r *mutationResolver) galleryUpdate(ctx context.Context, input models.Galle
}
// ensure that new primary file is associated with gallery
var f file.File
var f models.File
for _, ff := range originalGallery.Files.List() {
if ff.Base().ID == converted {
f = ff

View file

@ -123,7 +123,7 @@ func (r *mutationResolver) imageUpdate(ctx context.Context, input ImageUpdateInp
return nil, fmt.Errorf("converting primary file id: %w", err)
}
converted := file.ID(primaryFileID)
converted := models.FileID(primaryFileID)
updatedImage.PrimaryFileID = &converted
if err := i.LoadFiles(ctx, r.repository.Image); err != nil {
@ -131,7 +131,7 @@ func (r *mutationResolver) imageUpdate(ctx context.Context, input ImageUpdateInp
}
// ensure that new primary file is associated with image
var f file.File
var f models.File
for _, ff := range i.Files.List() {
if ff.Base().ID == converted {
f = ff

View file

@ -56,9 +56,9 @@ func (r *mutationResolver) SceneCreate(ctx context.Context, input SceneCreateInp
return nil, fmt.Errorf("converting file ids: %w", err)
}
fileIDs := make([]file.ID, len(fileIDsInt))
fileIDs := make([]models.FileID, len(fileIDsInt))
for i, v := range fileIDsInt {
fileIDs[i] = file.ID(v)
fileIDs[i] = models.FileID(v)
}
// Populate a new scene from the input
@ -212,7 +212,7 @@ func scenePartialFromInput(input models.SceneUpdateInput, translator changesetTr
return nil, fmt.Errorf("converting primary file id: %w", err)
}
converted := file.ID(primaryFileID)
converted := models.FileID(primaryFileID)
updatedScene.PrimaryFileID = &converted
}
@ -300,7 +300,7 @@ func (r *mutationResolver) sceneUpdate(ctx context.Context, input models.SceneUp
}
// ensure that new primary file is associated with scene
var f *file.VideoFile
var f *models.VideoFile
for _, ff := range originalScene.Files.List() {
if ff.ID == newPrimaryFileID {
f = ff
@ -575,7 +575,7 @@ func (r *mutationResolver) SceneAssignFile(ctx context.Context, input AssignScen
return false, fmt.Errorf("converting file ID: %w", err)
}
fileID := file.ID(fileIDInt)
fileID := models.FileID(fileIDInt)
if err := r.withTxn(ctx, func(ctx context.Context) error {
return r.Resolver.sceneService.AssignFile(ctx, sceneID, fileID)

View file

@ -22,14 +22,14 @@ import (
)
type ImageFinder interface {
Find(ctx context.Context, id int) (*models.Image, error)
models.ImageGetter
FindByChecksum(ctx context.Context, checksum string) ([]*models.Image, error)
}
type imageRoutes struct {
txnManager txn.Manager
imageFinder ImageFinder
fileFinder file.Finder
fileGetter models.FileGetter
}
func (rs imageRoutes) Routes() chi.Router {
@ -168,7 +168,7 @@ func (rs imageRoutes) ImageCtx(next http.Handler) http.Handler {
}
if image != nil {
if err := image.LoadPrimaryFile(ctx, rs.fileFinder); err != nil {
if err := image.LoadPrimaryFile(ctx, rs.fileGetter); err != nil {
if !errors.Is(err, context.Canceled) {
logger.Errorf("error loading primary file for image %d: %v", imageID, err)
}

View file

@ -14,9 +14,9 @@ import (
)
type MovieFinder interface {
models.MovieGetter
GetFrontImage(ctx context.Context, movieID int) ([]byte, error)
GetBackImage(ctx context.Context, movieID int) ([]byte, error)
Find(ctx context.Context, id int) (*models.Movie, error)
}
type movieRoutes struct {

View file

@ -15,7 +15,7 @@ import (
)
type PerformerFinder interface {
Find(ctx context.Context, id int) (*models.Performer, error)
models.PerformerGetter
GetImage(ctx context.Context, performerID int) ([]byte, error)
}

View file

@ -12,40 +12,43 @@ import (
"github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/pkg/ffmpeg"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/file/video"
"github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/scene"
"github.com/stashapp/stash/pkg/txn"
"github.com/stashapp/stash/pkg/utils"
)
type SceneFinder interface {
manager.SceneCoverGetter
models.SceneGetter
scene.IDFinder
FindByChecksum(ctx context.Context, checksum string) ([]*models.Scene, error)
FindByOSHash(ctx context.Context, oshash string) ([]*models.Scene, error)
GetCover(ctx context.Context, sceneID int) ([]byte, error)
}
type SceneMarkerFinder interface {
Find(ctx context.Context, id int) (*models.SceneMarker, error)
models.SceneMarkerGetter
FindBySceneID(ctx context.Context, sceneID int) ([]*models.SceneMarker, error)
}
type SceneMarkerTagFinder interface {
models.TagGetter
FindBySceneMarkerID(ctx context.Context, sceneMarkerID int) ([]*models.Tag, error)
}
type CaptionFinder interface {
GetCaptions(ctx context.Context, fileID file.ID) ([]*models.VideoCaption, error)
GetCaptions(ctx context.Context, fileID models.FileID) ([]*models.VideoCaption, error)
}
type sceneRoutes struct {
txnManager txn.Manager
sceneFinder SceneFinder
fileFinder file.Finder
fileGetter models.FileGetter
captionFinder CaptionFinder
sceneMarkerFinder SceneMarkerFinder
tagFinder scene.MarkerTagFinder
tagFinder SceneMarkerTagFinder
}
func (rs sceneRoutes) Routes() chi.Router {
@ -574,7 +577,7 @@ func (rs sceneRoutes) SceneCtx(next http.Handler) http.Handler {
scene, _ = qb.Find(ctx, sceneID)
if scene != nil {
if err := scene.LoadPrimaryFile(ctx, rs.fileFinder); err != nil {
if err := scene.LoadPrimaryFile(ctx, rs.fileGetter); err != nil {
if !errors.Is(err, context.Canceled) {
logger.Errorf("error loading primary file for scene %d: %v", sceneID, err)
}

View file

@ -11,13 +11,12 @@ import (
"github.com/stashapp/stash/internal/static"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/studio"
"github.com/stashapp/stash/pkg/txn"
"github.com/stashapp/stash/pkg/utils"
)
type StudioFinder interface {
studio.Finder
models.StudioGetter
GetImage(ctx context.Context, studioID int) ([]byte, error)
}

View file

@ -11,13 +11,12 @@ import (
"github.com/stashapp/stash/internal/static"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/tag"
"github.com/stashapp/stash/pkg/txn"
"github.com/stashapp/stash/pkg/utils"
)
type TagFinder interface {
tag.Finder
models.TagGetter
GetImage(ctx context.Context, tagID int) ([]byte, error)
}

View file

@ -151,7 +151,7 @@ func Start() error {
r.Mount("/scene", sceneRoutes{
txnManager: txnManager,
sceneFinder: txnManager.Scene,
fileFinder: txnManager.File,
fileGetter: txnManager.File,
captionFinder: txnManager.File,
sceneMarkerFinder: txnManager.SceneMarker,
tagFinder: txnManager.Tag,
@ -159,7 +159,7 @@ func Start() error {
r.Mount("/image", imageRoutes{
txnManager: txnManager,
imageFinder: txnManager.Image,
fileFinder: txnManager.File,
fileGetter: txnManager.File,
}.Routes())
r.Mount("/studio", studioRoutes{
txnManager: txnManager,

View file

@ -18,14 +18,6 @@ func handleFloat64(v float64) *float64 {
return &v
}
func handleFloat64Value(v float64) float64 {
if math.IsInf(v, 0) || math.IsNaN(v) {
return 0
}
return v
}
func translateUpdateIDs(strIDs []string, mode models.RelationshipUpdateMode) (*models.UpdateIDs, error) {
ids, err := stringslice.StringSliceToIntSlice(strIDs)
if err != nil {

View file

@ -9,14 +9,19 @@ import (
"github.com/stashapp/stash/pkg/sliceutil/intslice"
)
type GalleryFinderUpdater interface {
models.GalleryQueryer
models.GalleryUpdater
}
type GalleryPerformerUpdater interface {
models.PerformerIDLoader
gallery.PartialUpdater
models.GalleryUpdater
}
type GalleryTagUpdater interface {
models.TagIDLoader
gallery.PartialUpdater
models.GalleryUpdater
}
func getGalleryFileTagger(s *models.Gallery, cache *match.Cache) tagger {
@ -39,7 +44,7 @@ func getGalleryFileTagger(s *models.Gallery, cache *match.Cache) tagger {
}
// GalleryPerformers tags the provided gallery with performers whose name matches the gallery's path.
func GalleryPerformers(ctx context.Context, s *models.Gallery, rw GalleryPerformerUpdater, performerReader match.PerformerAutoTagQueryer, cache *match.Cache) error {
func GalleryPerformers(ctx context.Context, s *models.Gallery, rw GalleryPerformerUpdater, performerReader models.PerformerAutoTagQueryer, cache *match.Cache) error {
t := getGalleryFileTagger(s, cache)
return t.tagPerformers(ctx, performerReader, func(subjectID, otherID int) (bool, error) {
@ -63,7 +68,7 @@ func GalleryPerformers(ctx context.Context, s *models.Gallery, rw GalleryPerform
// GalleryStudios tags the provided gallery with the first studio whose name matches the gallery's path.
//
// Gallerys will not be tagged if studio is already set.
func GalleryStudios(ctx context.Context, s *models.Gallery, rw GalleryFinderUpdater, studioReader match.StudioAutoTagQueryer, cache *match.Cache) error {
func GalleryStudios(ctx context.Context, s *models.Gallery, rw GalleryFinderUpdater, studioReader models.StudioAutoTagQueryer, cache *match.Cache) error {
if s.StudioID != nil {
// don't modify
return nil
@ -77,7 +82,7 @@ func GalleryStudios(ctx context.Context, s *models.Gallery, rw GalleryFinderUpda
}
// GalleryTags tags the provided gallery with tags whose name matches the gallery's path.
func GalleryTags(ctx context.Context, s *models.Gallery, rw GalleryTagUpdater, tagReader match.TagAutoTagQueryer, cache *match.Cache) error {
func GalleryTags(ctx context.Context, s *models.Gallery, rw GalleryTagUpdater, tagReader models.TagAutoTagQueryer, cache *match.Cache) error {
t := getGalleryFileTagger(s, cache)
return t.tagTags(ctx, tagReader, func(subjectID, otherID int) (bool, error) {

View file

@ -9,14 +9,19 @@ import (
"github.com/stashapp/stash/pkg/sliceutil/intslice"
)
type ImageFinderUpdater interface {
models.ImageQueryer
models.ImageUpdater
}
type ImagePerformerUpdater interface {
models.PerformerIDLoader
image.PartialUpdater
models.ImageUpdater
}
type ImageTagUpdater interface {
models.TagIDLoader
image.PartialUpdater
models.ImageUpdater
}
func getImageFileTagger(s *models.Image, cache *match.Cache) tagger {
@ -30,7 +35,7 @@ func getImageFileTagger(s *models.Image, cache *match.Cache) tagger {
}
// ImagePerformers tags the provided image with performers whose name matches the image's path.
func ImagePerformers(ctx context.Context, s *models.Image, rw ImagePerformerUpdater, performerReader match.PerformerAutoTagQueryer, cache *match.Cache) error {
func ImagePerformers(ctx context.Context, s *models.Image, rw ImagePerformerUpdater, performerReader models.PerformerAutoTagQueryer, cache *match.Cache) error {
t := getImageFileTagger(s, cache)
return t.tagPerformers(ctx, performerReader, func(subjectID, otherID int) (bool, error) {
@ -54,7 +59,7 @@ func ImagePerformers(ctx context.Context, s *models.Image, rw ImagePerformerUpda
// ImageStudios tags the provided image with the first studio whose name matches the image's path.
//
// Images will not be tagged if studio is already set.
func ImageStudios(ctx context.Context, s *models.Image, rw ImageFinderUpdater, studioReader match.StudioAutoTagQueryer, cache *match.Cache) error {
func ImageStudios(ctx context.Context, s *models.Image, rw ImageFinderUpdater, studioReader models.StudioAutoTagQueryer, cache *match.Cache) error {
if s.StudioID != nil {
// don't modify
return nil
@ -68,7 +73,7 @@ func ImageStudios(ctx context.Context, s *models.Image, rw ImageFinderUpdater, s
}
// ImageTags tags the provided image with tags whose name matches the image's path.
func ImageTags(ctx context.Context, s *models.Image, rw ImageTagUpdater, tagReader match.TagAutoTagQueryer, cache *match.Cache) error {
func ImageTags(ctx context.Context, s *models.Image, rw ImageTagUpdater, tagReader models.TagAutoTagQueryer, cache *match.Cache) error {
t := getImageFileTagger(s, cache)
return t.tagTags(ctx, tagReader, func(subjectID, otherID int) (bool, error) {

View file

@ -10,7 +10,6 @@ import (
"path/filepath"
"testing"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/sqlite"
"github.com/stashapp/stash/pkg/txn"
@ -124,12 +123,12 @@ func createTag(ctx context.Context, qb models.TagWriter) error {
return nil
}
func createScenes(ctx context.Context, sqb models.SceneReaderWriter, folderStore file.FolderStore, fileStore file.Store) error {
func createScenes(ctx context.Context, sqb models.SceneReaderWriter, folderStore models.FolderFinderCreator, fileCreator models.FileCreator) error {
// create the scenes
scenePatterns, falseScenePatterns := generateTestPaths(testName, sceneExt)
for _, fn := range scenePatterns {
f, err := createSceneFile(ctx, fn, folderStore, fileStore)
f, err := createSceneFile(ctx, fn, folderStore, fileCreator)
if err != nil {
return err
}
@ -141,7 +140,7 @@ func createScenes(ctx context.Context, sqb models.SceneReaderWriter, folderStore
}
for _, fn := range falseScenePatterns {
f, err := createSceneFile(ctx, fn, folderStore, fileStore)
f, err := createSceneFile(ctx, fn, folderStore, fileCreator)
if err != nil {
return err
}
@ -154,7 +153,7 @@ func createScenes(ctx context.Context, sqb models.SceneReaderWriter, folderStore
// add organized scenes
for _, fn := range scenePatterns {
f, err := createSceneFile(ctx, "organized"+fn, folderStore, fileStore)
f, err := createSceneFile(ctx, "organized"+fn, folderStore, fileCreator)
if err != nil {
return err
}
@ -168,7 +167,7 @@ func createScenes(ctx context.Context, sqb models.SceneReaderWriter, folderStore
}
// create scene with existing studio io
f, err := createSceneFile(ctx, existingStudioSceneName, folderStore, fileStore)
f, err := createSceneFile(ctx, existingStudioSceneName, folderStore, fileCreator)
if err != nil {
return err
}
@ -196,7 +195,7 @@ func makeScene(expectedResult bool) *models.Scene {
return s
}
func createSceneFile(ctx context.Context, name string, folderStore file.FolderStore, fileStore file.Store) (*file.VideoFile, error) {
func createSceneFile(ctx context.Context, name string, folderStore models.FolderFinderCreator, fileCreator models.FileCreator) (*models.VideoFile, error) {
folderPath := filepath.Dir(name)
basename := filepath.Base(name)
@ -207,21 +206,21 @@ func createSceneFile(ctx context.Context, name string, folderStore file.FolderSt
folderID := folder.ID
f := &file.VideoFile{
BaseFile: &file.BaseFile{
f := &models.VideoFile{
BaseFile: &models.BaseFile{
Basename: basename,
ParentFolderID: folderID,
},
}
if err := fileStore.Create(ctx, f); err != nil {
if err := fileCreator.Create(ctx, f); err != nil {
return nil, fmt.Errorf("creating scene file %q: %w", name, err)
}
return f, nil
}
func getOrCreateFolder(ctx context.Context, folderStore file.FolderStore, folderPath string) (*file.Folder, error) {
func getOrCreateFolder(ctx context.Context, folderStore models.FolderFinderCreator, folderPath string) (*models.Folder, error) {
f, err := folderStore.FindByPath(ctx, folderPath)
if err != nil {
return nil, fmt.Errorf("getting folder by path: %w", err)
@ -231,7 +230,7 @@ func getOrCreateFolder(ctx context.Context, folderStore file.FolderStore, folder
return f, nil
}
var parentID file.FolderID
var parentID models.FolderID
dir := filepath.Dir(folderPath)
if dir != "." {
parent, err := getOrCreateFolder(ctx, folderStore, dir)
@ -242,7 +241,7 @@ func getOrCreateFolder(ctx context.Context, folderStore file.FolderStore, folder
parentID = parent.ID
}
f = &file.Folder{
f = &models.Folder{
Path: folderPath,
}
@ -257,8 +256,8 @@ func getOrCreateFolder(ctx context.Context, folderStore file.FolderStore, folder
return f, nil
}
func createScene(ctx context.Context, sqb models.SceneWriter, s *models.Scene, f *file.VideoFile) error {
err := sqb.Create(ctx, s, []file.ID{f.ID})
func createScene(ctx context.Context, sqb models.SceneWriter, s *models.Scene, f *models.VideoFile) error {
err := sqb.Create(ctx, s, []models.FileID{f.ID})
if err != nil {
return fmt.Errorf("Failed to create scene with path '%s': %s", f.Path, err.Error())
@ -267,12 +266,12 @@ func createScene(ctx context.Context, sqb models.SceneWriter, s *models.Scene, f
return nil
}
func createImages(ctx context.Context, w models.ImageReaderWriter, folderStore file.FolderStore, fileStore file.Store) error {
func createImages(ctx context.Context, w models.ImageReaderWriter, folderStore models.FolderFinderCreator, fileCreator models.FileCreator) error {
// create the images
imagePatterns, falseImagePatterns := generateTestPaths(testName, imageExt)
for _, fn := range imagePatterns {
f, err := createImageFile(ctx, fn, folderStore, fileStore)
f, err := createImageFile(ctx, fn, folderStore, fileCreator)
if err != nil {
return err
}
@ -283,7 +282,7 @@ func createImages(ctx context.Context, w models.ImageReaderWriter, folderStore f
}
}
for _, fn := range falseImagePatterns {
f, err := createImageFile(ctx, fn, folderStore, fileStore)
f, err := createImageFile(ctx, fn, folderStore, fileCreator)
if err != nil {
return err
}
@ -296,7 +295,7 @@ func createImages(ctx context.Context, w models.ImageReaderWriter, folderStore f
// add organized images
for _, fn := range imagePatterns {
f, err := createImageFile(ctx, "organized"+fn, folderStore, fileStore)
f, err := createImageFile(ctx, "organized"+fn, folderStore, fileCreator)
if err != nil {
return err
}
@ -310,7 +309,7 @@ func createImages(ctx context.Context, w models.ImageReaderWriter, folderStore f
}
// create image with existing studio io
f, err := createImageFile(ctx, existingStudioImageName, folderStore, fileStore)
f, err := createImageFile(ctx, existingStudioImageName, folderStore, fileCreator)
if err != nil {
return err
}
@ -326,7 +325,7 @@ func createImages(ctx context.Context, w models.ImageReaderWriter, folderStore f
return nil
}
func createImageFile(ctx context.Context, name string, folderStore file.FolderStore, fileStore file.Store) (*file.ImageFile, error) {
func createImageFile(ctx context.Context, name string, folderStore models.FolderFinderCreator, fileCreator models.FileCreator) (*models.ImageFile, error) {
folderPath := filepath.Dir(name)
basename := filepath.Base(name)
@ -337,14 +336,14 @@ func createImageFile(ctx context.Context, name string, folderStore file.FolderSt
folderID := folder.ID
f := &file.ImageFile{
BaseFile: &file.BaseFile{
f := &models.ImageFile{
BaseFile: &models.BaseFile{
Basename: basename,
ParentFolderID: folderID,
},
}
if err := fileStore.Create(ctx, f); err != nil {
if err := fileCreator.Create(ctx, f); err != nil {
return nil, err
}
@ -362,10 +361,10 @@ func makeImage(expectedResult bool) *models.Image {
return o
}
func createImage(ctx context.Context, w models.ImageWriter, o *models.Image, f *file.ImageFile) error {
func createImage(ctx context.Context, w models.ImageWriter, o *models.Image, f *models.ImageFile) error {
err := w.Create(ctx, &models.ImageCreateInput{
Image: o,
FileIDs: []file.ID{f.ID},
FileIDs: []models.FileID{f.ID},
})
if err != nil {
@ -375,12 +374,12 @@ func createImage(ctx context.Context, w models.ImageWriter, o *models.Image, f *
return nil
}
func createGalleries(ctx context.Context, w models.GalleryReaderWriter, folderStore file.FolderStore, fileStore file.Store) error {
func createGalleries(ctx context.Context, w models.GalleryReaderWriter, folderStore models.FolderFinderCreator, fileCreator models.FileCreator) error {
// create the galleries
galleryPatterns, falseGalleryPatterns := generateTestPaths(testName, galleryExt)
for _, fn := range galleryPatterns {
f, err := createGalleryFile(ctx, fn, folderStore, fileStore)
f, err := createGalleryFile(ctx, fn, folderStore, fileCreator)
if err != nil {
return err
}
@ -391,7 +390,7 @@ func createGalleries(ctx context.Context, w models.GalleryReaderWriter, folderSt
}
}
for _, fn := range falseGalleryPatterns {
f, err := createGalleryFile(ctx, fn, folderStore, fileStore)
f, err := createGalleryFile(ctx, fn, folderStore, fileCreator)
if err != nil {
return err
}
@ -404,7 +403,7 @@ func createGalleries(ctx context.Context, w models.GalleryReaderWriter, folderSt
// add organized galleries
for _, fn := range galleryPatterns {
f, err := createGalleryFile(ctx, "organized"+fn, folderStore, fileStore)
f, err := createGalleryFile(ctx, "organized"+fn, folderStore, fileCreator)
if err != nil {
return err
}
@ -418,7 +417,7 @@ func createGalleries(ctx context.Context, w models.GalleryReaderWriter, folderSt
}
// create gallery with existing studio io
f, err := createGalleryFile(ctx, existingStudioGalleryName, folderStore, fileStore)
f, err := createGalleryFile(ctx, existingStudioGalleryName, folderStore, fileCreator)
if err != nil {
return err
}
@ -434,7 +433,7 @@ func createGalleries(ctx context.Context, w models.GalleryReaderWriter, folderSt
return nil
}
func createGalleryFile(ctx context.Context, name string, folderStore file.FolderStore, fileStore file.Store) (*file.BaseFile, error) {
func createGalleryFile(ctx context.Context, name string, folderStore models.FolderFinderCreator, fileCreator models.FileCreator) (*models.BaseFile, error) {
folderPath := filepath.Dir(name)
basename := filepath.Base(name)
@ -445,12 +444,12 @@ func createGalleryFile(ctx context.Context, name string, folderStore file.Folder
folderID := folder.ID
f := &file.BaseFile{
f := &models.BaseFile{
Basename: basename,
ParentFolderID: folderID,
}
if err := fileStore.Create(ctx, f); err != nil {
if err := fileCreator.Create(ctx, f); err != nil {
return nil, err
}
@ -468,8 +467,8 @@ func makeGallery(expectedResult bool) *models.Gallery {
return o
}
func createGallery(ctx context.Context, w models.GalleryWriter, o *models.Gallery, f *file.BaseFile) error {
err := w.Create(ctx, o, []file.ID{f.ID})
func createGallery(ctx context.Context, w models.GalleryWriter, o *models.Gallery, f *models.BaseFile) error {
err := w.Create(ctx, o, []models.FileID{f.ID})
if err != nil {
return fmt.Errorf("Failed to create gallery with path '%s': %s", f.Path, err.Error())
}

View file

@ -13,21 +13,21 @@ import (
)
type SceneQueryPerformerUpdater interface {
scene.Queryer
models.SceneQueryer
models.PerformerIDLoader
scene.PartialUpdater
models.SceneUpdater
}
type ImageQueryPerformerUpdater interface {
image.Queryer
models.ImageQueryer
models.PerformerIDLoader
image.PartialUpdater
models.ImageUpdater
}
type GalleryQueryPerformerUpdater interface {
gallery.Queryer
models.GalleryQueryer
models.PerformerIDLoader
gallery.PartialUpdater
models.GalleryUpdater
}
func getPerformerTaggers(p *models.Performer, cache *match.Cache) []tagger {

View file

@ -9,14 +9,19 @@ import (
"github.com/stashapp/stash/pkg/sliceutil/intslice"
)
type SceneFinderUpdater interface {
models.SceneQueryer
models.SceneUpdater
}
type ScenePerformerUpdater interface {
models.PerformerIDLoader
scene.PartialUpdater
models.SceneUpdater
}
type SceneTagUpdater interface {
models.TagIDLoader
scene.PartialUpdater
models.SceneUpdater
}
func getSceneFileTagger(s *models.Scene, cache *match.Cache) tagger {
@ -30,7 +35,7 @@ func getSceneFileTagger(s *models.Scene, cache *match.Cache) tagger {
}
// ScenePerformers tags the provided scene with performers whose name matches the scene's path.
func ScenePerformers(ctx context.Context, s *models.Scene, rw ScenePerformerUpdater, performerReader match.PerformerAutoTagQueryer, cache *match.Cache) error {
func ScenePerformers(ctx context.Context, s *models.Scene, rw ScenePerformerUpdater, performerReader models.PerformerAutoTagQueryer, cache *match.Cache) error {
t := getSceneFileTagger(s, cache)
return t.tagPerformers(ctx, performerReader, func(subjectID, otherID int) (bool, error) {
@ -54,7 +59,7 @@ func ScenePerformers(ctx context.Context, s *models.Scene, rw ScenePerformerUpda
// SceneStudios tags the provided scene with the first studio whose name matches the scene's path.
//
// Scenes will not be tagged if studio is already set.
func SceneStudios(ctx context.Context, s *models.Scene, rw SceneFinderUpdater, studioReader match.StudioAutoTagQueryer, cache *match.Cache) error {
func SceneStudios(ctx context.Context, s *models.Scene, rw SceneFinderUpdater, studioReader models.StudioAutoTagQueryer, cache *match.Cache) error {
if s.StudioID != nil {
// don't modify
return nil
@ -68,7 +73,7 @@ func SceneStudios(ctx context.Context, s *models.Scene, rw SceneFinderUpdater, s
}
// SceneTags tags the provided scene with tags whose name matches the scene's path.
func SceneTags(ctx context.Context, s *models.Scene, rw SceneTagUpdater, tagReader match.TagAutoTagQueryer, cache *match.Cache) error {
func SceneTags(ctx context.Context, s *models.Scene, rw SceneTagUpdater, tagReader models.TagAutoTagQueryer, cache *match.Cache) error {
t := getSceneFileTagger(s, cache)
return t.tagTags(ctx, tagReader, func(subjectID, otherID int) (bool, error) {

View file

@ -3,18 +3,15 @@ package autotag
import (
"context"
"github.com/stashapp/stash/pkg/gallery"
"github.com/stashapp/stash/pkg/image"
"github.com/stashapp/stash/pkg/match"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/scene"
"github.com/stashapp/stash/pkg/txn"
)
// the following functions aren't used in Tagger because they assume
// use within a transaction
func addSceneStudio(ctx context.Context, sceneWriter scene.PartialUpdater, o *models.Scene, studioID int) (bool, error) {
func addSceneStudio(ctx context.Context, sceneWriter models.SceneUpdater, o *models.Scene, studioID int) (bool, error) {
// don't set if already set
if o.StudioID != nil {
return false, nil
@ -31,7 +28,7 @@ func addSceneStudio(ctx context.Context, sceneWriter scene.PartialUpdater, o *mo
return true, nil
}
func addImageStudio(ctx context.Context, imageWriter image.PartialUpdater, i *models.Image, studioID int) (bool, error) {
func addImageStudio(ctx context.Context, imageWriter models.ImageUpdater, i *models.Image, studioID int) (bool, error) {
// don't set if already set
if i.StudioID != nil {
return false, nil
@ -84,11 +81,6 @@ func getStudioTagger(p *models.Studio, aliases []string, cache *match.Cache) []t
return ret
}
type SceneFinderUpdater interface {
scene.Queryer
scene.PartialUpdater
}
// StudioScenes searches for scenes whose path matches the provided studio name and tags the scene with the studio, if studio is not already set on the scene.
func (tagger *Tagger) StudioScenes(ctx context.Context, p *models.Studio, paths []string, aliases []string, rw SceneFinderUpdater) error {
t := getStudioTagger(p, aliases, tagger.Cache)
@ -120,12 +112,6 @@ func (tagger *Tagger) StudioScenes(ctx context.Context, p *models.Studio, paths
return nil
}
type ImageFinderUpdater interface {
image.Queryer
Find(ctx context.Context, id int) (*models.Image, error)
UpdatePartial(ctx context.Context, id int, partial models.ImagePartial) (*models.Image, error)
}
// StudioImages searches for images whose path matches the provided studio name and tags the image with the studio, if studio is not already set on the image.
func (tagger *Tagger) StudioImages(ctx context.Context, p *models.Studio, paths []string, aliases []string, rw ImageFinderUpdater) error {
t := getStudioTagger(p, aliases, tagger.Cache)
@ -157,12 +143,6 @@ func (tagger *Tagger) StudioImages(ctx context.Context, p *models.Studio, paths
return nil
}
type GalleryFinderUpdater interface {
gallery.Queryer
gallery.PartialUpdater
Find(ctx context.Context, id int) (*models.Gallery, error)
}
// StudioGalleries searches for galleries whose path matches the provided studio name and tags the gallery with the studio, if studio is not already set on the gallery.
func (tagger *Tagger) StudioGalleries(ctx context.Context, p *models.Studio, paths []string, aliases []string, rw GalleryFinderUpdater) error {
t := getStudioTagger(p, aliases, tagger.Cache)

View file

@ -13,21 +13,21 @@ import (
)
type SceneQueryTagUpdater interface {
scene.Queryer
models.SceneQueryer
models.TagIDLoader
scene.PartialUpdater
models.SceneUpdater
}
type ImageQueryTagUpdater interface {
image.Queryer
models.ImageQueryer
models.TagIDLoader
image.PartialUpdater
models.ImageUpdater
}
type GalleryQueryTagUpdater interface {
gallery.Queryer
models.GalleryQueryer
models.TagIDLoader
gallery.PartialUpdater
models.GalleryUpdater
}
func getTagTaggers(p *models.Tag, aliases []string, cache *match.Cache) []tagger {

View file

@ -17,12 +17,9 @@ import (
"context"
"fmt"
"github.com/stashapp/stash/pkg/gallery"
"github.com/stashapp/stash/pkg/image"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/match"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/scene"
"github.com/stashapp/stash/pkg/txn"
)
@ -54,7 +51,7 @@ func (t *tagger) addLog(otherType, otherName string) {
logger.Infof("Added %s '%s' to %s '%s'", otherType, otherName, t.Type, t.Name)
}
func (t *tagger) tagPerformers(ctx context.Context, performerReader match.PerformerAutoTagQueryer, addFunc addLinkFunc) error {
func (t *tagger) tagPerformers(ctx context.Context, performerReader models.PerformerAutoTagQueryer, addFunc addLinkFunc) error {
others, err := match.PathToPerformers(ctx, t.Path, performerReader, t.cache, t.trimExt)
if err != nil {
return err
@ -75,7 +72,7 @@ func (t *tagger) tagPerformers(ctx context.Context, performerReader match.Perfor
return nil
}
func (t *tagger) tagStudios(ctx context.Context, studioReader match.StudioAutoTagQueryer, addFunc addLinkFunc) error {
func (t *tagger) tagStudios(ctx context.Context, studioReader models.StudioAutoTagQueryer, addFunc addLinkFunc) error {
studio, err := match.PathToStudio(ctx, t.Path, studioReader, t.cache, t.trimExt)
if err != nil {
return err
@ -96,7 +93,7 @@ func (t *tagger) tagStudios(ctx context.Context, studioReader match.StudioAutoTa
return nil
}
func (t *tagger) tagTags(ctx context.Context, tagReader match.TagAutoTagQueryer, addFunc addLinkFunc) error {
func (t *tagger) tagTags(ctx context.Context, tagReader models.TagAutoTagQueryer, addFunc addLinkFunc) error {
others, err := match.PathToTags(ctx, t.Path, tagReader, t.cache, t.trimExt)
if err != nil {
return err
@ -117,7 +114,7 @@ func (t *tagger) tagTags(ctx context.Context, tagReader match.TagAutoTagQueryer,
return nil
}
func (t *tagger) tagScenes(ctx context.Context, paths []string, sceneReader scene.Queryer, addFunc addSceneLinkFunc) error {
func (t *tagger) tagScenes(ctx context.Context, paths []string, sceneReader models.SceneQueryer, addFunc addSceneLinkFunc) error {
return match.PathToScenesFn(ctx, t.Name, paths, sceneReader, func(ctx context.Context, p *models.Scene) error {
added, err := addFunc(p)
@ -133,7 +130,7 @@ func (t *tagger) tagScenes(ctx context.Context, paths []string, sceneReader scen
})
}
func (t *tagger) tagImages(ctx context.Context, paths []string, imageReader image.Queryer, addFunc addImageLinkFunc) error {
func (t *tagger) tagImages(ctx context.Context, paths []string, imageReader models.ImageQueryer, addFunc addImageLinkFunc) error {
return match.PathToImagesFn(ctx, t.Name, paths, imageReader, func(ctx context.Context, p *models.Image) error {
added, err := addFunc(p)
@ -149,7 +146,7 @@ func (t *tagger) tagImages(ctx context.Context, paths []string, imageReader imag
})
}
func (t *tagger) tagGalleries(ctx context.Context, paths []string, galleryReader gallery.Queryer, addFunc addGalleryLinkFunc) error {
func (t *tagger) tagGalleries(ctx context.Context, paths []string, galleryReader models.GalleryQueryer, addFunc addGalleryLinkFunc) error {
return match.PathToGalleriesFn(ctx, t.Name, paths, galleryReader, func(ctx context.Context, p *models.Gallery) error {
added, err := addFunc(p)

View file

@ -363,7 +363,7 @@ func (me *contentDirectoryService) handleBrowseMetadata(obj object, host string)
if err := txn.WithReadTxn(context.TODO(), me.txnManager, func(ctx context.Context) error {
scene, err = me.repository.SceneFinder.Find(ctx, sceneID)
if scene != nil {
err = scene.LoadPrimaryFile(ctx, me.repository.FileFinder)
err = scene.LoadPrimaryFile(ctx, me.repository.FileGetter)
}
if err != nil {
@ -478,7 +478,7 @@ func (me *contentDirectoryService) getVideos(sceneFilter *models.SceneFilterType
}
} else {
for _, s := range scenes {
if err := s.LoadPrimaryFile(ctx, me.repository.FileFinder); err != nil {
if err := s.LoadPrimaryFile(ctx, me.repository.FileGetter); err != nil {
return err
}
@ -506,7 +506,7 @@ func (me *contentDirectoryService) getPageVideos(sceneFilter *models.SceneFilter
sort := me.VideoSortOrder
direction := getSortDirection(sceneFilter, sort)
var err error
objs, err = pager.getPageVideos(ctx, me.repository.SceneFinder, me.repository.FileFinder, page, host, sort, direction)
objs, err = pager.getPageVideos(ctx, me.repository.SceneFinder, me.repository.FileGetter, page, host, sort, direction)
if err != nil {
return err
}

View file

@ -48,13 +48,12 @@ import (
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/scene"
"github.com/stashapp/stash/pkg/txn"
)
type SceneFinder interface {
scene.Queryer
scene.IDFinder
models.SceneGetter
models.SceneQueryer
}
type StudioFinder interface {

View file

@ -6,7 +6,6 @@ import (
"math"
"strconv"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/scene"
)
@ -20,7 +19,7 @@ func (p *scenePager) getPageID(page int) string {
return p.parentID + "/page/" + strconv.Itoa(page)
}
func (p *scenePager) getPages(ctx context.Context, r scene.Queryer, total int) ([]interface{}, error) {
func (p *scenePager) getPages(ctx context.Context, r models.SceneQueryer, total int) ([]interface{}, error) {
var objs []interface{}
// get the first scene of each page to set an appropriate title
@ -60,7 +59,7 @@ func (p *scenePager) getPages(ctx context.Context, r scene.Queryer, total int) (
return objs, nil
}
func (p *scenePager) getPageVideos(ctx context.Context, r SceneFinder, f file.Finder, page int, host string, sort string, direction models.SortDirectionEnum) ([]interface{}, error) {
func (p *scenePager) getPageVideos(ctx context.Context, r SceneFinder, f models.FileGetter, page int, host string, sort string, direction models.SortDirectionEnum) ([]interface{}, error) {
var objs []interface{}
findFilter := &models.FindFilterType{

View file

@ -8,7 +8,6 @@ import (
"sync"
"time"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/txn"
@ -16,7 +15,7 @@ import (
type Repository struct {
SceneFinder SceneFinder
FileFinder file.Finder
FileGetter models.FileGetter
StudioFinder StudioFinder
TagFinder TagFinder
PerformerFinder PerformerFinder

View file

@ -46,7 +46,7 @@ type SceneIdentifier struct {
SceneReaderUpdater SceneReaderUpdater
StudioReaderWriter models.StudioReaderWriter
PerformerCreator PerformerCreator
TagCreatorFinder TagCreatorFinder
TagFinderCreator models.TagFinderCreator
DefaultOptions *MetadataOptions
Sources []ScraperSource
@ -176,7 +176,7 @@ func (t *SceneIdentifier) getSceneUpdater(ctx context.Context, s *models.Scene,
sceneReader: t.SceneReaderUpdater,
studioReaderWriter: t.StudioReaderWriter,
performerCreator: t.PerformerCreator,
tagCreatorFinder: t.TagCreatorFinder,
tagCreator: t.TagFinderCreator,
scene: s,
result: result,
fieldOptions: fieldOptions,
@ -332,7 +332,7 @@ func (t *SceneIdentifier) addTagToScene(ctx context.Context, txnManager txn.Mana
return err
}
ret, err := t.TagCreatorFinder.Find(ctx, tagID)
ret, err := t.TagFinderCreator.Find(ctx, tagID)
if err != nil {
logger.Infof("Added tag id %s to skipped scene %s", tagToAdd, s.Path)
} else {

View file

@ -186,7 +186,7 @@ func TestSceneIdentifier_Identify(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
identifier := SceneIdentifier{
SceneReaderUpdater: mockSceneReaderWriter,
TagCreatorFinder: mockTagFinderCreator,
TagFinderCreator: mockTagFinderCreator,
DefaultOptions: defaultOptions,
Sources: sources,
SceneUpdatePostHookExecutor: mockHookExecutor{},

View file

@ -10,7 +10,7 @@ import (
)
type PerformerCreator interface {
Create(ctx context.Context, newPerformer *models.Performer) error
models.PerformerCreator
UpdateImage(ctx context.Context, performerID int, image []byte) error
}

View file

@ -11,32 +11,29 @@ import (
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/scene"
"github.com/stashapp/stash/pkg/sliceutil"
"github.com/stashapp/stash/pkg/sliceutil/intslice"
"github.com/stashapp/stash/pkg/tag"
"github.com/stashapp/stash/pkg/utils"
)
type SceneReaderUpdater interface {
type SceneCoverGetter interface {
GetCover(ctx context.Context, sceneID int) ([]byte, error)
scene.Updater
}
type SceneReaderUpdater interface {
SceneCoverGetter
models.SceneUpdater
models.PerformerIDLoader
models.TagIDLoader
models.StashIDLoader
models.URLLoader
}
type TagCreatorFinder interface {
Create(ctx context.Context, newTag *models.Tag) error
tag.Finder
}
type sceneRelationships struct {
sceneReader SceneReaderUpdater
sceneReader SceneCoverGetter
studioReaderWriter models.StudioReaderWriter
performerCreator PerformerCreator
tagCreatorFinder TagCreatorFinder
tagCreator models.TagCreator
scene *models.Scene
result *scrapeResult
fieldOptions map[string]*FieldOptions
@ -173,7 +170,7 @@ func (g sceneRelationships) tags(ctx context.Context) ([]int, error) {
CreatedAt: now,
UpdatedAt: now,
}
err := g.tagCreatorFinder.Create(ctx, &newTag)
err := g.tagCreator.Create(ctx, &newTag)
if err != nil {
return nil, fmt.Errorf("error creating tag: %w", err)
}

View file

@ -378,7 +378,7 @@ func Test_sceneRelationships_tags(t *testing.T) {
tr := sceneRelationships{
sceneReader: mockSceneReaderWriter,
tagCreatorFinder: mockTagReaderWriter,
tagCreator: mockTagReaderWriter,
fieldOptions: make(map[string]*FieldOptions),
}

View file

@ -10,13 +10,14 @@ import (
"github.com/stashapp/stash/pkg/hash/md5"
"github.com/stashapp/stash/pkg/hash/oshash"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
)
type fingerprintCalculator struct {
Config *config.Instance
}
func (c *fingerprintCalculator) calculateOshash(f *file.BaseFile, o file.Opener) (*file.Fingerprint, error) {
func (c *fingerprintCalculator) calculateOshash(f *models.BaseFile, o file.Opener) (*models.Fingerprint, error) {
r, err := o.Open()
if err != nil {
return nil, fmt.Errorf("opening file: %w", err)
@ -34,13 +35,13 @@ func (c *fingerprintCalculator) calculateOshash(f *file.BaseFile, o file.Opener)
return nil, fmt.Errorf("calculating oshash: %w", err)
}
return &file.Fingerprint{
Type: file.FingerprintTypeOshash,
return &models.Fingerprint{
Type: models.FingerprintTypeOshash,
Fingerprint: hash,
}, nil
}
func (c *fingerprintCalculator) calculateMD5(o file.Opener) (*file.Fingerprint, error) {
func (c *fingerprintCalculator) calculateMD5(o file.Opener) (*models.Fingerprint, error) {
r, err := o.Open()
if err != nil {
return nil, fmt.Errorf("opening file: %w", err)
@ -53,24 +54,24 @@ func (c *fingerprintCalculator) calculateMD5(o file.Opener) (*file.Fingerprint,
return nil, fmt.Errorf("calculating md5: %w", err)
}
return &file.Fingerprint{
Type: file.FingerprintTypeMD5,
return &models.Fingerprint{
Type: models.FingerprintTypeMD5,
Fingerprint: hash,
}, nil
}
func (c *fingerprintCalculator) CalculateFingerprints(f *file.BaseFile, o file.Opener, useExisting bool) ([]file.Fingerprint, error) {
var ret []file.Fingerprint
func (c *fingerprintCalculator) CalculateFingerprints(f *models.BaseFile, o file.Opener, useExisting bool) ([]models.Fingerprint, error) {
var ret []models.Fingerprint
calculateMD5 := true
if useAsVideo(f.Path) {
var (
fp *file.Fingerprint
fp *models.Fingerprint
err error
)
if useExisting {
fp = f.Fingerprints.For(file.FingerprintTypeOshash)
fp = f.Fingerprints.For(models.FingerprintTypeOshash)
}
if fp == nil {
@ -89,12 +90,12 @@ func (c *fingerprintCalculator) CalculateFingerprints(f *file.BaseFile, o file.O
if calculateMD5 {
var (
fp *file.Fingerprint
fp *models.Fingerprint
err error
)
if useExisting {
fp = f.Fingerprints.For(file.FingerprintTypeMD5)
fp = f.Fingerprints.For(models.FingerprintTypeMD5)
}
if fp == nil {

View file

@ -26,6 +26,7 @@ import (
"github.com/stashapp/stash/pkg/image"
"github.com/stashapp/stash/pkg/job"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/paths"
"github.com/stashapp/stash/pkg/plugin"
"github.com/stashapp/stash/pkg/scene"
@ -222,7 +223,7 @@ func initialize() error {
instance.DLNAService = dlna.NewService(instance.Repository, dlna.Repository{
SceneFinder: instance.Repository.Scene,
FileFinder: instance.Repository.File,
FileGetter: instance.Repository.File,
StudioFinder: instance.Repository.Studio,
TagFinder: instance.Repository.Tag,
PerformerFinder: instance.Repository.Performer,
@ -280,15 +281,15 @@ func initialize() error {
return nil
}
func videoFileFilter(ctx context.Context, f file.File) bool {
func videoFileFilter(ctx context.Context, f models.File) bool {
return useAsVideo(f.Base().Path)
}
func imageFileFilter(ctx context.Context, f file.File) bool {
func imageFileFilter(ctx context.Context, f models.File) bool {
return useAsImage(f.Base().Path)
}
func galleryFileFilter(ctx context.Context, f file.File) bool {
func galleryFileFilter(ctx context.Context, f models.File) bool {
return isZip(f.Base().Basename)
}
@ -297,7 +298,7 @@ func makeScanner(db *sqlite.Database, pluginCache *plugin.Cache) *file.Scanner {
Repository: file.Repository{
Manager: db,
DatabaseProvider: db,
Store: db.File,
FileStore: db.File,
FolderStore: db.Folder,
},
FileDecorators: []file.Decorator{
@ -325,7 +326,7 @@ func makeCleaner(db *sqlite.Database, pluginCache *plugin.Cache) *file.Cleaner {
Repository: file.Repository{
Manager: db,
DatabaseProvider: db,
Store: db.File,
FileStore: db.File,
FolderStore: db.Folder,
},
Handlers: []file.CleanHandler{

View file

@ -3,8 +3,6 @@ package manager
import (
"context"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/gallery"
"github.com/stashapp/stash/pkg/image"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/scene"
@ -12,49 +10,17 @@ import (
"github.com/stashapp/stash/pkg/txn"
)
type ImageReaderWriter interface {
models.ImageReaderWriter
image.FinderCreatorUpdater
GetManyFileIDs(ctx context.Context, ids []int) ([][]file.ID, error)
}
type GalleryReaderWriter interface {
models.GalleryReaderWriter
gallery.FinderCreatorUpdater
gallery.Finder
models.FileLoader
GetManyFileIDs(ctx context.Context, ids []int) ([][]file.ID, error)
}
type SceneReaderWriter interface {
models.SceneReaderWriter
scene.CreatorUpdater
models.URLLoader
GetManyFileIDs(ctx context.Context, ids []int) ([][]file.ID, error)
}
type FileReaderWriter interface {
file.Store
Query(ctx context.Context, options models.FileQueryOptions) (*models.FileQueryResult, error)
GetCaptions(ctx context.Context, fileID file.ID) ([]*models.VideoCaption, error)
IsPrimary(ctx context.Context, fileID file.ID) (bool, error)
}
type FolderReaderWriter interface {
file.FolderStore
}
type Repository struct {
models.TxnManager
File FileReaderWriter
Folder FolderReaderWriter
Gallery GalleryReaderWriter
File models.FileReaderWriter
Folder models.FolderReaderWriter
Gallery models.GalleryReaderWriter
GalleryChapter models.GalleryChapterReaderWriter
Image ImageReaderWriter
Image models.ImageReaderWriter
Movie models.MovieReaderWriter
Performer models.PerformerReaderWriter
Scene SceneReaderWriter
Scene models.SceneReaderWriter
SceneMarker models.SceneMarkerReaderWriter
Studio models.StudioReaderWriter
Tag models.TagReaderWriter
@ -94,15 +60,15 @@ func sqliteRepository(d *sqlite.Database) Repository {
}
type SceneService interface {
Create(ctx context.Context, input *models.Scene, fileIDs []file.ID, coverImage []byte) (*models.Scene, error)
AssignFile(ctx context.Context, sceneID int, fileID file.ID) error
Create(ctx context.Context, input *models.Scene, fileIDs []models.FileID, coverImage []byte) (*models.Scene, error)
AssignFile(ctx context.Context, sceneID int, fileID models.FileID) error
Merge(ctx context.Context, sourceIDs []int, destinationID int, values models.ScenePartial) error
Destroy(ctx context.Context, scene *models.Scene, fileDeleter *scene.FileDeleter, deleteGenerated, deleteFile bool) error
}
type ImageService interface {
Destroy(ctx context.Context, image *models.Image, fileDeleter *image.FileDeleter, deleteGenerated, deleteFile bool) error
DestroyZipImages(ctx context.Context, zipFile file.File, fileDeleter *image.FileDeleter, deleteGenerated bool) ([]*models.Image, error)
DestroyZipImages(ctx context.Context, zipFile models.File, fileDeleter *image.FileDeleter, deleteGenerated bool) ([]*models.Image, error)
}
type GalleryService interface {

View file

@ -6,7 +6,6 @@ import (
"github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/pkg/ffmpeg"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/models"
)
@ -57,7 +56,7 @@ var (
}
)
func GetVideoFileContainer(file *file.VideoFile) (ffmpeg.Container, error) {
func GetVideoFileContainer(file *models.VideoFile) (ffmpeg.Container, error) {
var container ffmpeg.Container
format := file.Format
if format != "" {
@ -88,7 +87,7 @@ func GetSceneStreamPaths(scene *models.Scene, directStreamURL *url.URL, maxStrea
// convert StreamingResolutionEnum to ResolutionEnum
maxStreamingResolution := models.ResolutionEnum(maxStreamingTranscodeSize)
sceneResolution := file.GetMinResolution(pf)
sceneResolution := models.GetMinResolution(pf)
includeSceneStreamPath := func(streamingResolution models.StreamingResolutionEnum) bool {
var minResolution int
if streamingResolution == models.StreamingResolutionEnumOriginal {

View file

@ -257,7 +257,7 @@ type cleanHandler struct {
PluginCache *plugin.Cache
}
func (h *cleanHandler) HandleFile(ctx context.Context, fileDeleter *file.Deleter, fileID file.ID) error {
func (h *cleanHandler) HandleFile(ctx context.Context, fileDeleter *file.Deleter, fileID models.FileID) error {
if err := h.handleRelatedScenes(ctx, fileDeleter, fileID); err != nil {
return err
}
@ -271,11 +271,11 @@ func (h *cleanHandler) HandleFile(ctx context.Context, fileDeleter *file.Deleter
return nil
}
func (h *cleanHandler) HandleFolder(ctx context.Context, fileDeleter *file.Deleter, folderID file.FolderID) error {
func (h *cleanHandler) HandleFolder(ctx context.Context, fileDeleter *file.Deleter, folderID models.FolderID) error {
return h.deleteRelatedFolderGalleries(ctx, folderID)
}
func (h *cleanHandler) handleRelatedScenes(ctx context.Context, fileDeleter *file.Deleter, fileID file.ID) error {
func (h *cleanHandler) handleRelatedScenes(ctx context.Context, fileDeleter *file.Deleter, fileID models.FileID) error {
mgr := GetInstance()
sceneQB := mgr.Database.Scene
scenes, err := sceneQB.FindByFileID(ctx, fileID)
@ -313,7 +313,7 @@ func (h *cleanHandler) handleRelatedScenes(ctx context.Context, fileDeleter *fil
}, nil)
} else {
// set the primary file to a remaining file
var newPrimaryID file.ID
var newPrimaryID models.FileID
for _, f := range scene.Files.List() {
if f.ID != fileID {
newPrimaryID = f.ID
@ -332,7 +332,7 @@ func (h *cleanHandler) handleRelatedScenes(ctx context.Context, fileDeleter *fil
return nil
}
func (h *cleanHandler) handleRelatedGalleries(ctx context.Context, fileID file.ID) error {
func (h *cleanHandler) handleRelatedGalleries(ctx context.Context, fileID models.FileID) error {
mgr := GetInstance()
qb := mgr.Database.Gallery
galleries, err := qb.FindByFileID(ctx, fileID)
@ -358,7 +358,7 @@ func (h *cleanHandler) handleRelatedGalleries(ctx context.Context, fileID file.I
}, nil)
} else {
// set the primary file to a remaining file
var newPrimaryID file.ID
var newPrimaryID models.FileID
for _, f := range g.Files.List() {
if f.Base().ID != fileID {
newPrimaryID = f.Base().ID
@ -377,7 +377,7 @@ func (h *cleanHandler) handleRelatedGalleries(ctx context.Context, fileID file.I
return nil
}
func (h *cleanHandler) deleteRelatedFolderGalleries(ctx context.Context, folderID file.FolderID) error {
func (h *cleanHandler) deleteRelatedFolderGalleries(ctx context.Context, folderID models.FolderID) error {
mgr := GetInstance()
qb := mgr.Database.Gallery
galleries, err := qb.FindByFolderID(ctx, folderID)
@ -401,7 +401,7 @@ func (h *cleanHandler) deleteRelatedFolderGalleries(ctx context.Context, folderI
return nil
}
func (h *cleanHandler) handleRelatedImages(ctx context.Context, fileDeleter *file.Deleter, fileID file.ID) error {
func (h *cleanHandler) handleRelatedImages(ctx context.Context, fileDeleter *file.Deleter, fileID models.FileID) error {
mgr := GetInstance()
imageQB := mgr.Database.Image
images, err := imageQB.FindByFileID(ctx, fileID)
@ -431,7 +431,7 @@ func (h *cleanHandler) handleRelatedImages(ctx context.Context, fileDeleter *fil
}, nil)
} else {
// set the primary file to a remaining file
var newPrimaryID file.ID
var newPrimaryID models.FileID
for _, f := range i.Files.List() {
if f.Base().ID != fileID {
newPrimaryID = f.Base().ID

View file

@ -13,7 +13,6 @@ import (
"time"
"github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/gallery"
"github.com/stashapp/stash/pkg/image"
@ -386,7 +385,7 @@ func (t *ExportTask) ExportScenes(ctx context.Context, workers int, repo Reposit
logger.Infof("[scenes] export complete in %s. %d workers used.", time.Since(startTime), workers)
}
func exportFile(f file.File, t *ExportTask) {
func exportFile(f models.File, t *ExportTask) {
newFileJSON := fileToJSON(f)
fn := newFileJSON.Filename()
@ -396,7 +395,7 @@ func exportFile(f file.File, t *ExportTask) {
}
}
func fileToJSON(f file.File) jsonschema.DirEntry {
func fileToJSON(f models.File) jsonschema.DirEntry {
bf := f.Base()
base := jsonschema.BaseFile{
@ -422,7 +421,7 @@ func fileToJSON(f file.File) jsonschema.DirEntry {
}
switch ff := f.(type) {
case *file.VideoFile:
case *models.VideoFile:
base.Type = jsonschema.DirEntryTypeVideo
return jsonschema.VideoFile{
BaseFile: &base,
@ -437,7 +436,7 @@ func fileToJSON(f file.File) jsonschema.DirEntry {
Interactive: ff.Interactive,
InteractiveSpeed: ff.InteractiveSpeed,
}
case *file.ImageFile:
case *models.ImageFile:
base.Type = jsonschema.DirEntryTypeImage
return jsonschema.ImageFile{
BaseFile: &base,
@ -450,7 +449,7 @@ func fileToJSON(f file.File) jsonschema.DirEntry {
return &base
}
func exportFolder(f file.Folder, t *ExportTask) {
func exportFolder(f models.Folder, t *ExportTask) {
newFileJSON := folderToJSON(f)
fn := newFileJSON.Filename()
@ -460,7 +459,7 @@ func exportFolder(f file.Folder, t *ExportTask) {
}
}
func folderToJSON(f file.Folder) jsonschema.DirEntry {
func folderToJSON(f models.Folder) jsonschema.DirEntry {
base := jsonschema.BaseDirEntry{
Type: jsonschema.DirEntryTypeFolder,
ModTime: json.JSONTime{Time: f.ModTime},

View file

@ -4,7 +4,6 @@ import (
"context"
"fmt"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/image"
"github.com/stashapp/stash/pkg/logger"
@ -44,7 +43,7 @@ func (t *GenerateClipPreviewTask) Start(ctx context.Context) {
}
func (t *GenerateClipPreviewTask) required() bool {
_, ok := t.Image.Files.Primary().(*file.VideoFile)
_, ok := t.Image.Files.Primary().(*models.VideoFile)
if !ok {
return false
}

View file

@ -5,7 +5,6 @@ import (
"fmt"
"path/filepath"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
@ -102,7 +101,7 @@ func (t *GenerateMarkersTask) generateSceneMarkers(ctx context.Context) {
}
}
func (t *GenerateMarkersTask) generateMarker(videoFile *file.VideoFile, scene *models.Scene, sceneMarker *models.SceneMarker) {
func (t *GenerateMarkersTask) generateMarker(videoFile *models.VideoFile, scene *models.Scene, sceneMarker *models.SceneMarker) {
sceneHash := t.Scene.GetHash(t.fileNamingAlgorithm)
seconds := int(sceneMarker.Seconds)

View file

@ -4,7 +4,6 @@ import (
"context"
"fmt"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/hash/videophash"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
@ -12,11 +11,11 @@ import (
)
type GeneratePhashTask struct {
File *file.VideoFile
File *models.VideoFile
Overwrite bool
fileNamingAlgorithm models.HashAlgorithm
txnManager txn.Manager
fileUpdater file.Updater
fileUpdater models.FileUpdater
}
func (t *GeneratePhashTask) GetDescription() string {
@ -38,8 +37,8 @@ func (t *GeneratePhashTask) Start(ctx context.Context) {
if err := txn.WithTxn(ctx, t.txnManager, func(ctx context.Context) error {
qb := t.fileUpdater
hashValue := int64(*hash)
t.File.Fingerprints = t.File.Fingerprints.AppendUnique(file.Fingerprint{
Type: file.FingerprintTypePhash,
t.File.Fingerprints = t.File.Fingerprints.AppendUnique(models.Fingerprint{
Type: models.FingerprintTypePhash,
Fingerprint: hashValue,
})
@ -54,5 +53,5 @@ func (t *GeneratePhashTask) required() bool {
return true
}
return t.File.Fingerprints.Get(file.FingerprintTypePhash) == nil
return t.File.Fingerprints.Get(models.FingerprintTypePhash) == nil
}

View file

@ -136,7 +136,7 @@ func (j *IdentifyJob) identifyScene(ctx context.Context, s *models.Scene, source
SceneReaderUpdater: instance.Repository.Scene,
StudioReaderWriter: instance.Repository.Studio,
PerformerCreator: instance.Repository.Performer,
TagCreatorFinder: instance.Repository.Tag,
TagFinderCreator: instance.Repository.Tag,
DefaultOptions: j.input.Options,
Sources: sources,

View file

@ -10,6 +10,7 @@ import (
"path/filepath"
"github.com/99designs/gqlgen/graphql"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/gallery"
"github.com/stashapp/stash/pkg/image"
@ -281,7 +282,7 @@ func (t *ImportTask) ImportStudios(ctx context.Context) {
logger.Info("[studios] import complete")
}
func (t *ImportTask) ImportStudio(ctx context.Context, studioJSON *jsonschema.Studio, pendingParent map[string][]*jsonschema.Studio, readerWriter studio.NameFinderCreatorUpdater) error {
func (t *ImportTask) ImportStudio(ctx context.Context, studioJSON *jsonschema.Studio, pendingParent map[string][]*jsonschema.Studio, readerWriter studio.ImporterReaderWriter) error {
importer := &studio.Importer{
ReaderWriter: readerWriter,
Input: *studioJSON,
@ -385,7 +386,7 @@ func (t *ImportTask) ImportFiles(ctx context.Context) {
if err := t.txnManager.WithTxn(ctx, func(ctx context.Context) error {
return t.ImportFile(ctx, fileJSON, pendingParent)
}); err != nil {
if errors.Is(err, errZipFileNotExist) {
if errors.Is(err, file.ErrZipFileNotExist) {
// add to the pending parent list so that it is created after the parent
s := pendingParent[fileJSON.DirEntry().ZipFile]
s = append(s, fileJSON)
@ -421,7 +422,7 @@ func (t *ImportTask) ImportFile(ctx context.Context, fileJSON jsonschema.DirEntr
r := t.txnManager
readerWriter := r.File
fileImporter := &fileFolderImporter{
fileImporter := &file.Importer{
ReaderWriter: readerWriter,
FolderStore: r.Folder,
Input: fileJSON,
@ -569,7 +570,7 @@ func (t *ImportTask) ImportTags(ctx context.Context) {
logger.Info("[tags] import complete")
}
func (t *ImportTask) ImportTag(ctx context.Context, tagJSON *jsonschema.Tag, pendingParent map[string][]*jsonschema.Tag, fail bool, readerWriter tag.NameFinderCreatorUpdater) error {
func (t *ImportTask) ImportTag(ctx context.Context, tagJSON *jsonschema.Tag, pendingParent map[string][]*jsonschema.Tag, fail bool, readerWriter tag.ImporterReaderWriter) error {
importer := &tag.Importer{
ReaderWriter: readerWriter,
Input: *tagJSON,

View file

@ -96,17 +96,17 @@ func newExtensionConfig(c *config.Instance) extensionConfig {
}
type fileCounter interface {
CountByFileID(ctx context.Context, fileID file.ID) (int, error)
CountByFileID(ctx context.Context, fileID models.FileID) (int, error)
}
type galleryFinder interface {
fileCounter
FindByFolderID(ctx context.Context, folderID file.FolderID) ([]*models.Gallery, error)
FindByFolderID(ctx context.Context, folderID models.FolderID) ([]*models.Gallery, error)
}
type sceneFinder interface {
fileCounter
FindByPrimaryFileID(ctx context.Context, fileID file.ID) ([]*models.Scene, error)
FindByPrimaryFileID(ctx context.Context, fileID models.FileID) ([]*models.Scene, error)
}
// handlerRequiredFilter returns true if a File's handler needs to be executed despite the file not being updated.
@ -139,7 +139,7 @@ func newHandlerRequiredFilter(c *config.Instance) *handlerRequiredFilter {
}
}
func (f *handlerRequiredFilter) Accept(ctx context.Context, ff file.File) bool {
func (f *handlerRequiredFilter) Accept(ctx context.Context, ff models.File) bool {
path := ff.Base().Path
isVideoFile := useAsVideo(path)
isImageFile := useAsImage(path)
@ -213,7 +213,7 @@ func (f *handlerRequiredFilter) Accept(ctx context.Context, ff file.File) bool {
// clean captions - scene handler handles this as well, but
// unchanged files aren't processed by the scene handler
videoFile, _ := ff.(*file.VideoFile)
videoFile, _ := ff.(*models.VideoFile)
if videoFile != nil {
if err := video.CleanCaptions(ctx, videoFile, f.txnManager, f.CaptionUpdater); err != nil {
logger.Errorf("Error cleaning captions: %v", err)
@ -370,7 +370,7 @@ type imageGenerators struct {
progress *job.Progress
}
func (g *imageGenerators) Generate(ctx context.Context, i *models.Image, f file.File) error {
func (g *imageGenerators) Generate(ctx context.Context, i *models.Image, f models.File) error {
const overwrite = false
progress := g.progress
@ -387,12 +387,12 @@ func (g *imageGenerators) Generate(ctx context.Context, i *models.Image, f file.
}
// avoid adding a task if the file isn't a video file
_, isVideo := f.(*file.VideoFile)
_, isVideo := f.(*models.VideoFile)
if isVideo && t.ScanGenerateClipPreviews {
// this is a bit of a hack: the task requires files to be loaded, but
// we don't really need to since we already have the file
ii := *i
ii.Files = models.NewRelatedFiles([]file.File{f})
ii.Files = models.NewRelatedFiles([]models.File{f})
progress.AddTotal(1)
previewsFn := func(ctx context.Context) {
@ -415,7 +415,7 @@ func (g *imageGenerators) Generate(ctx context.Context, i *models.Image, f file.
return nil
}
func (g *imageGenerators) generateThumbnail(ctx context.Context, i *models.Image, f file.File) error {
func (g *imageGenerators) generateThumbnail(ctx context.Context, i *models.Image, f models.File) error {
thumbPath := GetInstance().Paths.Generated.GetThumbnailPath(i.Checksum, models.DefaultGthumbWidth)
exists, _ := fsutil.FileExists(thumbPath)
if exists {
@ -424,12 +424,12 @@ func (g *imageGenerators) generateThumbnail(ctx context.Context, i *models.Image
path := f.Base().Path
asFrame, ok := f.(file.VisualFile)
vf, ok := f.(models.VisualFile)
if !ok {
return fmt.Errorf("file %s does not implement Frame", path)
return fmt.Errorf("file %s is not a visual file", path)
}
if asFrame.GetHeight() <= models.DefaultGthumbWidth && asFrame.GetWidth() <= models.DefaultGthumbWidth {
if vf.GetHeight() <= models.DefaultGthumbWidth && vf.GetWidth() <= models.DefaultGthumbWidth {
return nil
}
@ -466,7 +466,7 @@ type sceneGenerators struct {
progress *job.Progress
}
func (g *sceneGenerators) Generate(ctx context.Context, s *models.Scene, f *file.VideoFile) error {
func (g *sceneGenerators) Generate(ctx context.Context, s *models.Scene, f *models.VideoFile) error {
const overwrite = false
progress := g.progress

View file

@ -16,7 +16,6 @@ import (
"sync/atomic"
"time"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
@ -51,7 +50,7 @@ const (
type StreamType struct {
Name string
SegmentType *SegmentType
ServeManifest func(sm *StreamManager, w http.ResponseWriter, r *http.Request, vf *file.VideoFile, resolution string)
ServeManifest func(sm *StreamManager, w http.ResponseWriter, r *http.Request, vf *models.VideoFile, resolution string)
Args func(codec VideoCodec, segment int, videoFilter VideoFilter, videoOnly bool, outputDir string) Args
}
@ -250,7 +249,7 @@ var ErrInvalidSegment = errors.New("invalid segment")
type StreamOptions struct {
StreamType *StreamType
VideoFile *file.VideoFile
VideoFile *models.VideoFile
Resolution string
Hash string
Segment string
@ -279,7 +278,7 @@ type waitingSegment struct {
type runningStream struct {
dir string
streamType *StreamType
vf *file.VideoFile
vf *models.VideoFile
maxTranscodeSize int
outputDir string
@ -394,7 +393,7 @@ func (tp *transcodeProcess) checkSegments() {
}
}
func lastSegment(vf *file.VideoFile) int {
func lastSegment(vf *models.VideoFile) int {
return int(math.Ceil(vf.Duration/segmentLength)) - 1
}
@ -405,7 +404,7 @@ func segmentExists(path string) bool {
// serveHLSManifest serves a generated HLS playlist. The URLs for the segments
// are of the form {r.URL}/%d.ts{?urlQuery} where %d is the segment index.
func serveHLSManifest(sm *StreamManager, w http.ResponseWriter, r *http.Request, vf *file.VideoFile, resolution string) {
func serveHLSManifest(sm *StreamManager, w http.ResponseWriter, r *http.Request, vf *models.VideoFile, resolution string) {
if sm.cacheDir == "" {
logger.Error("[transcode] cannot live transcode with HLS because cache dir is unset")
http.Error(w, "cannot live transcode with HLS because cache dir is unset", http.StatusServiceUnavailable)
@ -460,7 +459,7 @@ func serveHLSManifest(sm *StreamManager, w http.ResponseWriter, r *http.Request,
}
// serveDASHManifest serves a generated DASH manifest.
func serveDASHManifest(sm *StreamManager, w http.ResponseWriter, r *http.Request, vf *file.VideoFile, resolution string) {
func serveDASHManifest(sm *StreamManager, w http.ResponseWriter, r *http.Request, vf *models.VideoFile, resolution string) {
if sm.cacheDir == "" {
logger.Error("[transcode] cannot live transcode with DASH because cache dir is unset")
http.Error(w, "cannot live transcode files with DASH because cache dir is unset", http.StatusServiceUnavailable)
@ -550,7 +549,7 @@ func serveDASHManifest(sm *StreamManager, w http.ResponseWriter, r *http.Request
utils.ServeStaticContent(w, r, buf.Bytes())
}
func (sm *StreamManager) ServeManifest(w http.ResponseWriter, r *http.Request, streamType *StreamType, vf *file.VideoFile, resolution string) {
func (sm *StreamManager) ServeManifest(w http.ResponseWriter, r *http.Request, streamType *StreamType, vf *models.VideoFile, resolution string) {
streamType.ServeManifest(sm, w, r, vf, resolution)
}

View file

@ -8,7 +8,6 @@ import (
"strings"
"syscall"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
@ -134,7 +133,7 @@ var (
type TranscodeOptions struct {
StreamType StreamFormat
VideoFile *file.VideoFile
VideoFile *models.VideoFile
Resolution string
StartTime float64
}

View file

@ -10,12 +10,13 @@ import (
"github.com/stashapp/stash/pkg/job"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/txn"
)
// Cleaner scans through stored file and folder instances and removes those that are no longer present on disk.
type Cleaner struct {
FS FS
FS models.FS
Repository Repository
Handlers []CleanHandler
@ -55,44 +56,44 @@ func (s *Cleaner) Clean(ctx context.Context, options CleanOptions, progress *job
}
type fileOrFolder struct {
fileID ID
folderID FolderID
fileID models.FileID
folderID models.FolderID
}
type deleteSet struct {
orderedList []fileOrFolder
fileIDSet map[ID]string
fileIDSet map[models.FileID]string
folderIDSet map[FolderID]string
folderIDSet map[models.FolderID]string
}
func newDeleteSet() deleteSet {
return deleteSet{
fileIDSet: make(map[ID]string),
folderIDSet: make(map[FolderID]string),
fileIDSet: make(map[models.FileID]string),
folderIDSet: make(map[models.FolderID]string),
}
}
func (s *deleteSet) add(id ID, path string) {
func (s *deleteSet) add(id models.FileID, path string) {
if _, ok := s.fileIDSet[id]; !ok {
s.orderedList = append(s.orderedList, fileOrFolder{fileID: id})
s.fileIDSet[id] = path
}
}
func (s *deleteSet) has(id ID) bool {
func (s *deleteSet) has(id models.FileID) bool {
_, ok := s.fileIDSet[id]
return ok
}
func (s *deleteSet) addFolder(id FolderID, path string) {
func (s *deleteSet) addFolder(id models.FolderID, path string) {
if _, ok := s.folderIDSet[id]; !ok {
s.orderedList = append(s.orderedList, fileOrFolder{folderID: id})
s.folderIDSet[id] = path
}
}
func (s *deleteSet) hasFolder(id FolderID) bool {
func (s *deleteSet) hasFolder(id models.FolderID) bool {
_, ok := s.folderIDSet[id]
return ok
}
@ -113,7 +114,7 @@ func (j *cleanJob) execute(ctx context.Context) error {
if err := txn.WithReadTxn(ctx, j.Repository, func(ctx context.Context) error {
var err error
fileCount, err = j.Repository.CountAllInPaths(ctx, j.options.Paths)
fileCount, err = j.Repository.FileStore.CountAllInPaths(ctx, j.options.Paths)
if err != nil {
return err
}
@ -177,7 +178,7 @@ func (j *cleanJob) assessFiles(ctx context.Context, toDelete *deleteSet) error {
return nil
}
files, err := j.Repository.FindAllInPaths(ctx, j.options.Paths, batchSize, offset)
files, err := j.Repository.FileStore.FindAllInPaths(ctx, j.options.Paths, batchSize, offset)
if err != nil {
return fmt.Errorf("error querying for files: %w", err)
}
@ -221,9 +222,9 @@ func (j *cleanJob) assessFiles(ctx context.Context, toDelete *deleteSet) error {
}
// flagFolderForDelete adds folders to the toDelete set, with the leaf folders added first
func (j *cleanJob) flagFileForDelete(ctx context.Context, toDelete *deleteSet, f File) error {
func (j *cleanJob) flagFileForDelete(ctx context.Context, toDelete *deleteSet, f models.File) error {
// add contained files first
containedFiles, err := j.Repository.FindByZipFileID(ctx, f.Base().ID)
containedFiles, err := j.Repository.FileStore.FindByZipFileID(ctx, f.Base().ID)
if err != nil {
return fmt.Errorf("error finding contained files for %q: %w", f.Base().Path, err)
}
@ -306,7 +307,7 @@ func (j *cleanJob) assessFolders(ctx context.Context, toDelete *deleteSet) error
return nil
}
func (j *cleanJob) flagFolderForDelete(ctx context.Context, toDelete *deleteSet, folder *Folder) error {
func (j *cleanJob) flagFolderForDelete(ctx context.Context, toDelete *deleteSet, folder *models.Folder) error {
// it is possible that child folders may be included while parent folders are not
// so we need to check child folders separately
toDelete.addFolder(folder.ID, folder.Path)
@ -314,7 +315,7 @@ func (j *cleanJob) flagFolderForDelete(ctx context.Context, toDelete *deleteSet,
return nil
}
func (j *cleanJob) shouldClean(ctx context.Context, f File) bool {
func (j *cleanJob) shouldClean(ctx context.Context, f models.File) bool {
path := f.Base().Path
info, err := f.Base().Info(j.FS)
@ -336,7 +337,7 @@ func (j *cleanJob) shouldClean(ctx context.Context, f File) bool {
return !filter.Accept(ctx, path, info)
}
func (j *cleanJob) shouldCleanFolder(ctx context.Context, f *Folder) bool {
func (j *cleanJob) shouldCleanFolder(ctx context.Context, f *models.Folder) bool {
path := f.Path
info, err := f.Info(j.FS)
@ -376,7 +377,7 @@ func (j *cleanJob) shouldCleanFolder(ctx context.Context, f *Folder) bool {
return !filter.Accept(ctx, path, info)
}
func (j *cleanJob) deleteFile(ctx context.Context, fileID ID, fn string) {
func (j *cleanJob) deleteFile(ctx context.Context, fileID models.FileID, fn string) {
// delete associated objects
fileDeleter := NewDeleter()
if err := txn.WithTxn(ctx, j.Repository, func(ctx context.Context) error {
@ -386,14 +387,14 @@ func (j *cleanJob) deleteFile(ctx context.Context, fileID ID, fn string) {
return err
}
return j.Repository.Destroy(ctx, fileID)
return j.Repository.FileStore.Destroy(ctx, fileID)
}); err != nil {
logger.Errorf("Error deleting file %q from database: %s", fn, err.Error())
return
}
}
func (j *cleanJob) deleteFolder(ctx context.Context, folderID FolderID, fn string) {
func (j *cleanJob) deleteFolder(ctx context.Context, folderID models.FolderID, fn string) {
// delete associated objects
fileDeleter := NewDeleter()
if err := txn.WithTxn(ctx, j.Repository, func(ctx context.Context) error {
@ -410,7 +411,7 @@ func (j *cleanJob) deleteFolder(ctx context.Context, folderID FolderID, fn strin
}
}
func (j *cleanJob) fireHandlers(ctx context.Context, fileDeleter *Deleter, fileID ID) error {
func (j *cleanJob) fireHandlers(ctx context.Context, fileDeleter *Deleter, fileID models.FileID) error {
for _, h := range j.Handlers {
if err := h.HandleFile(ctx, fileDeleter, fileID); err != nil {
return err
@ -420,7 +421,7 @@ func (j *cleanJob) fireHandlers(ctx context.Context, fileDeleter *Deleter, fileI
return nil
}
func (j *cleanJob) fireFolderHandlers(ctx context.Context, fileDeleter *Deleter, folderID FolderID) error {
func (j *cleanJob) fireFolderHandlers(ctx context.Context, fileDeleter *Deleter, folderID models.FolderID) error {
for _, h := range j.Handlers {
if err := h.HandleFolder(ctx, fileDeleter, folderID); err != nil {
return err

View file

@ -9,6 +9,7 @@ import (
"github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/txn"
)
@ -179,7 +180,7 @@ func (d *Deleter) renameForRestore(path string) error {
return d.RenamerRemover.Rename(path+deleteFileSuffix, path)
}
func Destroy(ctx context.Context, destroyer Destroyer, f File, fileDeleter *Deleter, deleteFile bool) error {
func Destroy(ctx context.Context, destroyer models.FileDestroyer, f models.File, fileDeleter *Deleter, deleteFile bool) error {
if err := destroyer.Destroy(ctx, f.Base().ID); err != nil {
return err
}
@ -195,11 +196,11 @@ func Destroy(ctx context.Context, destroyer Destroyer, f File, fileDeleter *Dele
}
type ZipDestroyer struct {
FileDestroyer GetterDestroyer
FolderDestroyer FolderGetterDestroyer
FileDestroyer models.FileFinderDestroyer
FolderDestroyer models.FolderFinderDestroyer
}
func (d *ZipDestroyer) DestroyZip(ctx context.Context, f File, fileDeleter *Deleter, deleteFile bool) error {
func (d *ZipDestroyer) DestroyZip(ctx context.Context, f models.File, fileDeleter *Deleter, deleteFile bool) error {
// destroy contained files
files, err := d.FileDestroyer.FindByZipFileID(ctx, f.Base().ID)
if err != nil {

View file

@ -1,225 +1,15 @@
package file
import (
"bytes"
"context"
"io"
"io/fs"
"net/http"
"strconv"
"time"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/txn"
)
// ID represents an ID of a file.
type ID int32
// Repository provides access to storage methods for files and folders.
type Repository struct {
txn.Manager
txn.DatabaseProvider
func (i ID) String() string {
return strconv.Itoa(int(i))
}
// DirEntry represents a file or directory in the file system.
type DirEntry struct {
ZipFileID *ID `json:"zip_file_id"`
// transient - not persisted
// only guaranteed to have id, path and basename set
ZipFile File
ModTime time.Time `json:"mod_time"`
}
func (e *DirEntry) info(fs FS, path string) (fs.FileInfo, error) {
if e.ZipFile != nil {
zipPath := e.ZipFile.Base().Path
zfs, err := fs.OpenZip(zipPath)
if err != nil {
return nil, err
}
defer zfs.Close()
fs = zfs
}
// else assume os file
ret, err := fs.Lstat(path)
return ret, err
}
// File represents a file in the file system.
type File interface {
Base() *BaseFile
SetFingerprints(fp Fingerprints)
Open(fs FS) (io.ReadCloser, error)
}
// BaseFile represents a file in the file system.
type BaseFile struct {
ID ID `json:"id"`
DirEntry
// resolved from parent folder and basename only - not stored in DB
Path string `json:"path"`
Basename string `json:"basename"`
ParentFolderID FolderID `json:"parent_folder_id"`
Fingerprints Fingerprints `json:"fingerprints"`
Size int64 `json:"size"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// SetFingerprints sets the fingerprints of the file.
// If a fingerprint of the same type already exists, it is overwritten.
func (f *BaseFile) SetFingerprints(fp Fingerprints) {
for _, v := range fp {
f.SetFingerprint(v)
}
}
// SetFingerprint sets the fingerprint of the file.
// If a fingerprint of the same type already exists, it is overwritten.
func (f *BaseFile) SetFingerprint(fp Fingerprint) {
for i, existing := range f.Fingerprints {
if existing.Type == fp.Type {
f.Fingerprints[i] = fp
return
}
}
f.Fingerprints = append(f.Fingerprints, fp)
}
// Base is used to fulfil the File interface.
func (f *BaseFile) Base() *BaseFile {
return f
}
func (f *BaseFile) Open(fs FS) (io.ReadCloser, error) {
if f.ZipFile != nil {
zipPath := f.ZipFile.Base().Path
zfs, err := fs.OpenZip(zipPath)
if err != nil {
return nil, err
}
return zfs.OpenOnly(f.Path)
}
return fs.Open(f.Path)
}
func (f *BaseFile) Info(fs FS) (fs.FileInfo, error) {
return f.info(fs, f.Path)
}
func (f *BaseFile) Serve(fs FS, w http.ResponseWriter, r *http.Request) error {
reader, err := f.Open(fs)
if err != nil {
return err
}
defer reader.Close()
content, ok := reader.(io.ReadSeeker)
if !ok {
data, err := io.ReadAll(reader)
if err != nil {
return err
}
content = bytes.NewReader(data)
}
if r.URL.Query().Has("t") {
w.Header().Set("Cache-Control", "private, max-age=31536000, immutable")
} else {
w.Header().Set("Cache-Control", "no-cache")
}
http.ServeContent(w, r, f.Basename, f.ModTime, content)
return nil
}
type Finder interface {
Find(ctx context.Context, id ...ID) ([]File, error)
}
// Getter provides methods to find Files.
type Getter interface {
Finder
FindByPath(ctx context.Context, path string) (File, error)
FindAllByPath(ctx context.Context, path string) ([]File, error)
FindByFingerprint(ctx context.Context, fp Fingerprint) ([]File, error)
FindByZipFileID(ctx context.Context, zipFileID ID) ([]File, error)
FindAllInPaths(ctx context.Context, p []string, limit, offset int) ([]File, error)
FindByFileInfo(ctx context.Context, info fs.FileInfo, size int64) ([]File, error)
}
type Counter interface {
CountAllInPaths(ctx context.Context, p []string) (int, error)
CountByFolderID(ctx context.Context, folderID FolderID) (int, error)
}
// Creator provides methods to create Files.
type Creator interface {
Create(ctx context.Context, f File) error
}
// Updater provides methods to update Files.
type Updater interface {
Update(ctx context.Context, f File) error
}
type Destroyer interface {
Destroy(ctx context.Context, id ID) error
}
type GetterUpdater interface {
Getter
Updater
}
type GetterDestroyer interface {
Getter
Destroyer
}
// Store provides methods to find, create and update Files.
type Store interface {
Getter
Counter
Creator
Updater
Destroyer
IsPrimary(ctx context.Context, fileID ID) (bool, error)
}
// Decorator wraps the Decorate method to add additional functionality while scanning files.
type Decorator interface {
Decorate(ctx context.Context, fs FS, f File) (File, error)
IsMissingMetadata(ctx context.Context, fs FS, f File) bool
}
type FilteredDecorator struct {
Decorator
Filter
}
// Decorate runs the decorator if the filter accepts the file.
func (d *FilteredDecorator) Decorate(ctx context.Context, fs FS, f File) (File, error) {
if d.Accept(ctx, f) {
return d.Decorator.Decorate(ctx, fs, f)
}
return f, nil
}
func (d *FilteredDecorator) IsMissingMetadata(ctx context.Context, fs FS, f File) bool {
if d.Accept(ctx, f) {
return d.Decorator.IsMissingMetadata(ctx, fs, f)
}
return false
FileStore models.FileReaderWriter
FolderStore models.FolderReaderWriter
}

View file

@ -3,94 +3,16 @@ package file
import (
"context"
"fmt"
"io/fs"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/stashapp/stash/pkg/models"
)
// FolderID represents an ID of a folder.
type FolderID int32
// String converts the ID to a string.
func (i FolderID) String() string {
return strconv.Itoa(int(i))
}
// Folder represents a folder in the file system.
type Folder struct {
ID FolderID `json:"id"`
DirEntry
Path string `json:"path"`
ParentFolderID *FolderID `json:"parent_folder_id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (f *Folder) Info(fs FS) (fs.FileInfo, error) {
return f.info(fs, f.Path)
}
type FolderFinder interface {
Find(ctx context.Context, id FolderID) (*Folder, error)
}
// FolderPathFinder finds Folders by their path.
type FolderPathFinder interface {
FindByPath(ctx context.Context, path string) (*Folder, error)
}
// FolderGetter provides methods to find Folders.
type FolderGetter interface {
FolderFinder
FolderPathFinder
FindByZipFileID(ctx context.Context, zipFileID ID) ([]*Folder, error)
FindAllInPaths(ctx context.Context, p []string, limit, offset int) ([]*Folder, error)
FindByParentFolderID(ctx context.Context, parentFolderID FolderID) ([]*Folder, error)
}
type FolderCounter interface {
CountAllInPaths(ctx context.Context, p []string) (int, error)
}
// FolderCreator provides methods to create Folders.
type FolderCreator interface {
Create(ctx context.Context, f *Folder) error
}
type FolderFinderCreator interface {
FolderPathFinder
FolderCreator
}
// FolderUpdater provides methods to update Folders.
type FolderUpdater interface {
Update(ctx context.Context, f *Folder) error
}
type FolderDestroyer interface {
Destroy(ctx context.Context, id FolderID) error
}
type FolderGetterDestroyer interface {
FolderGetter
FolderDestroyer
}
// FolderStore provides methods to find, create and update Folders.
type FolderStore interface {
FolderGetter
FolderCounter
FolderCreator
FolderUpdater
FolderDestroyer
}
// GetOrCreateFolderHierarchy gets the folder for the given path, or creates a folder hierarchy for the given path if one if no existing folder is found.
// Does not create any folders in the file system
func GetOrCreateFolderHierarchy(ctx context.Context, fc FolderFinderCreator, path string) (*Folder, error) {
func GetOrCreateFolderHierarchy(ctx context.Context, fc models.FolderFinderCreator, path string) (*models.Folder, error) {
// get or create folder hierarchy
folder, err := fc.FindByPath(ctx, path)
if err != nil {
@ -106,10 +28,10 @@ func GetOrCreateFolderHierarchy(ctx context.Context, fc FolderFinderCreator, pat
now := time.Now()
folder = &Folder{
folder = &models.Folder{
Path: path,
ParentFolderID: &parent.ID,
DirEntry: DirEntry{
DirEntry: models.DirEntry{
// leave mod time empty for now - it will be updated when the folder is scanned
},
CreatedAt: now,
@ -126,7 +48,7 @@ func GetOrCreateFolderHierarchy(ctx context.Context, fc FolderFinderCreator, pat
// TransferZipFolderHierarchy creates the folder hierarchy for zipFileID under newPath, and removes
// ZipFileID from folders under oldPath.
func TransferZipFolderHierarchy(ctx context.Context, folderStore FolderStore, zipFileID ID, oldPath string, newPath string) error {
func TransferZipFolderHierarchy(ctx context.Context, folderStore models.FolderReaderWriter, zipFileID models.FileID, oldPath string, newPath string) error {
zipFolders, err := folderStore.FindByZipFileID(ctx, zipFileID)
if err != nil {
return err

View file

@ -7,27 +7,28 @@ import (
"io/fs"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
)
type folderRenameCandidate struct {
folder *Folder
folder *models.Folder
found int
files int
}
type folderRenameDetector struct {
// candidates is a map of folder id to the number of files that match
candidates map[FolderID]folderRenameCandidate
candidates map[models.FolderID]folderRenameCandidate
// rejects is a set of folder ids which were found to still exist
rejects map[FolderID]struct{}
rejects map[models.FolderID]struct{}
}
func (d *folderRenameDetector) isReject(id FolderID) bool {
func (d *folderRenameDetector) isReject(id models.FolderID) bool {
_, ok := d.rejects[id]
return ok
}
func (d *folderRenameDetector) getCandidate(id FolderID) *folderRenameCandidate {
func (d *folderRenameDetector) getCandidate(id models.FolderID) *folderRenameCandidate {
c, ok := d.candidates[id]
if !ok {
return nil
@ -40,14 +41,14 @@ func (d *folderRenameDetector) setCandidate(c folderRenameCandidate) {
d.candidates[c.folder.ID] = c
}
func (d *folderRenameDetector) reject(id FolderID) {
func (d *folderRenameDetector) reject(id models.FolderID) {
d.rejects[id] = struct{}{}
}
// bestCandidate returns the folder that is the best candidate for a rename.
// This is the folder that has the largest number of its original files that
// are still present in the new location.
func (d *folderRenameDetector) bestCandidate() *Folder {
func (d *folderRenameDetector) bestCandidate() *models.Folder {
if len(d.candidates) == 0 {
return nil
}
@ -74,14 +75,14 @@ func (d *folderRenameDetector) bestCandidate() *Folder {
return best.folder
}
func (s *scanJob) detectFolderMove(ctx context.Context, file scanFile) (*Folder, error) {
func (s *scanJob) detectFolderMove(ctx context.Context, file scanFile) (*models.Folder, error) {
// in order for a folder to be considered moved, the existing folder must be
// missing, and the majority of the old folder's files must be present, unchanged,
// in the new folder.
detector := folderRenameDetector{
candidates: make(map[FolderID]folderRenameCandidate),
rejects: make(map[FolderID]struct{}),
candidates: make(map[models.FolderID]folderRenameCandidate),
rejects: make(map[models.FolderID]struct{}),
}
// rejects is a set of folder ids which were found to still exist
@ -117,7 +118,7 @@ func (s *scanJob) detectFolderMove(ctx context.Context, file scanFile) (*Folder,
}
// check if the file exists in the database based on basename, size and mod time
existing, err := s.Repository.Store.FindByFileInfo(ctx, info, size)
existing, err := s.Repository.FileStore.FindByFileInfo(ctx, info, size)
if err != nil {
return fmt.Errorf("checking for existing file %q: %w", path, err)
}
@ -163,7 +164,7 @@ func (s *scanJob) detectFolderMove(ctx context.Context, file scanFile) (*Folder,
// parent folder is missing, possible candidate
// count the total number of files in the existing folder
count, err := s.Repository.Store.CountByFolderID(ctx, parentFolderID)
count, err := s.Repository.FileStore.CountByFolderID(ctx, parentFolderID)
if err != nil {
return fmt.Errorf("counting files in folder %d: %w", parentFolderID, err)
}

View file

@ -1,20 +0,0 @@
package file
// VisualFile is an interface for files that have a width and height.
type VisualFile interface {
File
GetWidth() int
GetHeight() int
GetFormat() string
}
func GetMinResolution(f VisualFile) int {
w := f.GetWidth()
h := f.GetHeight()
if w < h {
return w
}
return h
}

View file

@ -6,6 +6,7 @@ import (
"os"
"github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/models"
)
// Opener provides an interface to open a file.
@ -14,7 +15,7 @@ type Opener interface {
}
type fsOpener struct {
fs FS
fs models.FS
name string
}
@ -22,15 +23,6 @@ func (o *fsOpener) Open() (io.ReadCloser, error) {
return o.fs.Open(o.name)
}
// FS represents a file system.
type FS interface {
Stat(name string) (fs.FileInfo, error)
Lstat(name string) (fs.FileInfo, error)
Open(name string) (fs.ReadDirFile, error)
OpenZip(name string) (*ZipFS, error)
IsPathCaseSensitive(path string) (bool, error)
}
// OsFS is a file system backed by the OS.
type OsFS struct{}
@ -66,7 +58,7 @@ func (f *OsFS) Open(name string) (fs.ReadDirFile, error) {
return os.Open(name)
}
func (f *OsFS) OpenZip(name string) (*ZipFS, error) {
func (f *OsFS) OpenZip(name string) (models.ZipFS, error) {
info, err := f.Lstat(name)
if err != nil {
return nil, err

View file

@ -3,6 +3,8 @@ package file
import (
"context"
"io/fs"
"github.com/stashapp/stash/pkg/models"
)
// PathFilter provides a filter function for paths.
@ -18,18 +20,18 @@ func (pff PathFilterFunc) Accept(path string) bool {
// Filter provides a filter function for Files.
type Filter interface {
Accept(ctx context.Context, f File) bool
Accept(ctx context.Context, f models.File) bool
}
type FilterFunc func(ctx context.Context, f File) bool
type FilterFunc func(ctx context.Context, f models.File) bool
func (ff FilterFunc) Accept(ctx context.Context, f File) bool {
func (ff FilterFunc) Accept(ctx context.Context, f models.File) bool {
return ff(ctx, f)
}
// Handler provides a handler for Files.
type Handler interface {
Handle(ctx context.Context, f File, oldFile File) error
Handle(ctx context.Context, f models.File, oldFile models.File) error
}
// FilteredHandler is a Handler runs only if the filter accepts the file.
@ -39,7 +41,7 @@ type FilteredHandler struct {
}
// Handle runs the handler if the filter accepts the file.
func (h *FilteredHandler) Handle(ctx context.Context, f File, oldFile File) error {
func (h *FilteredHandler) Handle(ctx context.Context, f models.File, oldFile models.File) error {
if h.Accept(ctx, f) {
return h.Handler.Handle(ctx, f, oldFile)
}
@ -48,6 +50,6 @@ func (h *FilteredHandler) Handle(ctx context.Context, f File, oldFile File) erro
// CleanHandler provides a handler for cleaning Files and Folders.
type CleanHandler interface {
HandleFile(ctx context.Context, fileDeleter *Deleter, fileID ID) error
HandleFolder(ctx context.Context, fileDeleter *Deleter, folderID FolderID) error
HandleFile(ctx context.Context, fileDeleter *Deleter, fileID models.FileID) error
HandleFolder(ctx context.Context, fileDeleter *Deleter, folderID models.FolderID) error
}

View file

@ -13,6 +13,7 @@ import (
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/file/video"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
_ "golang.org/x/image/webp"
)
@ -21,10 +22,10 @@ type Decorator struct {
FFProbe ffmpeg.FFProbe
}
func (d *Decorator) Decorate(ctx context.Context, fs file.FS, f file.File) (file.File, error) {
func (d *Decorator) Decorate(ctx context.Context, fs models.FS, f models.File) (models.File, error) {
base := f.Base()
decorateFallback := func() (file.File, error) {
decorateFallback := func() (models.File, error) {
r, err := fs.Open(base.Path)
if err != nil {
return f, fmt.Errorf("reading image file %q: %w", base.Path, err)
@ -35,7 +36,7 @@ func (d *Decorator) Decorate(ctx context.Context, fs file.FS, f file.File) (file
if err != nil {
return f, fmt.Errorf("decoding image file %q: %w", base.Path, err)
}
return &file.ImageFile{
return &models.ImageFile{
BaseFile: base,
Format: format,
Width: c.Width,
@ -58,7 +59,7 @@ func (d *Decorator) Decorate(ctx context.Context, fs file.FS, f file.File) (file
// Fallback to catch non-animated avif images that FFProbe detects as video files
if probe.Bitrate == 0 && probe.VideoCodec == "av1" {
return &file.ImageFile{
return &models.ImageFile{
BaseFile: base,
Format: "avif",
Width: probe.Width,
@ -78,7 +79,7 @@ func (d *Decorator) Decorate(ctx context.Context, fs file.FS, f file.File) (file
return videoFileDecorator.Decorate(ctx, fs, f)
}
return &file.ImageFile{
return &models.ImageFile{
BaseFile: base,
Format: probe.VideoCodec,
Width: probe.Width,
@ -86,14 +87,14 @@ func (d *Decorator) Decorate(ctx context.Context, fs file.FS, f file.File) (file
}, nil
}
func (d *Decorator) IsMissingMetadata(ctx context.Context, fs file.FS, f file.File) bool {
func (d *Decorator) IsMissingMetadata(ctx context.Context, fs models.FS, f models.File) bool {
const (
unsetString = "unset"
unsetNumber = -1
)
imf, isImage := f.(*file.ImageFile)
vf, isVideo := f.(*file.VideoFile)
imf, isImage := f.(*models.ImageFile)
vf, isVideo := f.(*models.VideoFile)
switch {
case isImage:

View file

@ -1,21 +0,0 @@
package file
// ImageFile is an extension of BaseFile to represent image files.
type ImageFile struct {
*BaseFile
Format string `json:"format"`
Width int `json:"width"`
Height int `json:"height"`
}
func (f ImageFile) GetWidth() int {
return f.Width
}
func (f ImageFile) GetHeight() int {
return f.Height
}
func (f ImageFile) GetFormat() string {
return f.Format
}

View file

@ -1,4 +1,4 @@
package manager
package file
import (
"context"
@ -7,24 +7,22 @@ import (
"path/filepath"
"time"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/jsonschema"
)
// HACK: this is all here because of an import loop in jsonschema -> models -> file
var ErrZipFileNotExist = errors.New("zip file does not exist")
var errZipFileNotExist = errors.New("zip file does not exist")
type fileFolderImporter struct {
ReaderWriter file.Store
FolderStore file.FolderStore
type Importer struct {
ReaderWriter models.FileFinderCreator
FolderStore models.FolderFinderCreator
Input jsonschema.DirEntry
file file.File
folder *file.Folder
file models.File
folder *models.Folder
}
func (i *fileFolderImporter) PreImport(ctx context.Context) error {
func (i *Importer) PreImport(ctx context.Context) error {
var err error
switch ff := i.Input.(type) {
@ -37,9 +35,9 @@ func (i *fileFolderImporter) PreImport(ctx context.Context) error {
return err
}
func (i *fileFolderImporter) folderJSONToFolder(ctx context.Context, baseJSON *jsonschema.BaseDirEntry) (*file.Folder, error) {
ret := file.Folder{
DirEntry: file.DirEntry{
func (i *Importer) folderJSONToFolder(ctx context.Context, baseJSON *jsonschema.BaseDirEntry) (*models.Folder, error) {
ret := models.Folder{
DirEntry: models.DirEntry{
ModTime: baseJSON.ModTime.GetTime(),
},
Path: baseJSON.Path,
@ -56,14 +54,14 @@ func (i *fileFolderImporter) folderJSONToFolder(ctx context.Context, baseJSON *j
return &ret, nil
}
func (i *fileFolderImporter) fileJSONToFile(ctx context.Context, fileJSON jsonschema.DirEntry) (file.File, error) {
func (i *Importer) fileJSONToFile(ctx context.Context, fileJSON jsonschema.DirEntry) (models.File, error) {
switch ff := fileJSON.(type) {
case *jsonschema.VideoFile:
baseFile, err := i.baseFileJSONToBaseFile(ctx, ff.BaseFile)
if err != nil {
return nil, err
}
return &file.VideoFile{
return &models.VideoFile{
BaseFile: baseFile,
Format: ff.Format,
Width: ff.Width,
@ -81,7 +79,7 @@ func (i *fileFolderImporter) fileJSONToFile(ctx context.Context, fileJSON jsonsc
if err != nil {
return nil, err
}
return &file.ImageFile{
return &models.ImageFile{
BaseFile: baseFile,
Format: ff.Format,
Width: ff.Width,
@ -94,9 +92,9 @@ func (i *fileFolderImporter) fileJSONToFile(ctx context.Context, fileJSON jsonsc
return nil, fmt.Errorf("unknown file type")
}
func (i *fileFolderImporter) baseFileJSONToBaseFile(ctx context.Context, baseJSON *jsonschema.BaseFile) (*file.BaseFile, error) {
baseFile := file.BaseFile{
DirEntry: file.DirEntry{
func (i *Importer) baseFileJSONToBaseFile(ctx context.Context, baseJSON *jsonschema.BaseFile) (*models.BaseFile, error) {
baseFile := models.BaseFile{
DirEntry: models.DirEntry{
ModTime: baseJSON.ModTime.GetTime(),
},
Basename: filepath.Base(baseJSON.Path),
@ -106,7 +104,7 @@ func (i *fileFolderImporter) baseFileJSONToBaseFile(ctx context.Context, baseJSO
}
for _, fp := range baseJSON.Fingerprints {
baseFile.Fingerprints = append(baseFile.Fingerprints, file.Fingerprint{
baseFile.Fingerprints = append(baseFile.Fingerprints, models.Fingerprint{
Type: fp.Type,
Fingerprint: fp.Fingerprint,
})
@ -119,7 +117,7 @@ func (i *fileFolderImporter) baseFileJSONToBaseFile(ctx context.Context, baseJSO
return &baseFile, nil
}
func (i *fileFolderImporter) populateZipFileID(ctx context.Context, f *file.DirEntry) error {
func (i *Importer) populateZipFileID(ctx context.Context, f *models.DirEntry) error {
zipFilePath := i.Input.DirEntry().ZipFile
if zipFilePath != "" {
zf, err := i.ReaderWriter.FindByPath(ctx, zipFilePath)
@ -128,7 +126,7 @@ func (i *fileFolderImporter) populateZipFileID(ctx context.Context, f *file.DirE
}
if zf == nil {
return errZipFileNotExist
return ErrZipFileNotExist
}
id := zf.Base().ID
@ -138,15 +136,15 @@ func (i *fileFolderImporter) populateZipFileID(ctx context.Context, f *file.DirE
return nil
}
func (i *fileFolderImporter) PostImport(ctx context.Context, id int) error {
func (i *Importer) PostImport(ctx context.Context, id int) error {
return nil
}
func (i *fileFolderImporter) Name() string {
func (i *Importer) Name() string {
return i.Input.DirEntry().Path
}
func (i *fileFolderImporter) FindExistingID(ctx context.Context) (*int, error) {
func (i *Importer) FindExistingID(ctx context.Context) (*int, error) {
path := i.Input.DirEntry().Path
existing, err := i.ReaderWriter.FindByPath(ctx, path)
if err != nil {
@ -161,7 +159,7 @@ func (i *fileFolderImporter) FindExistingID(ctx context.Context) (*int, error) {
return nil, nil
}
func (i *fileFolderImporter) createFolderHierarchy(ctx context.Context, p string) (*file.Folder, error) {
func (i *Importer) createFolderHierarchy(ctx context.Context, p string) (*models.Folder, error) {
parentPath := filepath.Dir(p)
if parentPath == p {
@ -177,7 +175,7 @@ func (i *fileFolderImporter) createFolderHierarchy(ctx context.Context, p string
return i.getOrCreateFolder(ctx, p, parent)
}
func (i *fileFolderImporter) getOrCreateFolder(ctx context.Context, path string, parent *file.Folder) (*file.Folder, error) {
func (i *Importer) getOrCreateFolder(ctx context.Context, path string, parent *models.Folder) (*models.Folder, error) {
folder, err := i.FolderStore.FindByPath(ctx, path)
if err != nil {
return nil, err
@ -189,7 +187,7 @@ func (i *fileFolderImporter) getOrCreateFolder(ctx context.Context, path string,
now := time.Now()
folder = &file.Folder{
folder = &models.Folder{
Path: path,
CreatedAt: now,
UpdatedAt: now,
@ -207,7 +205,7 @@ func (i *fileFolderImporter) getOrCreateFolder(ctx context.Context, path string,
return folder, nil
}
func (i *fileFolderImporter) Create(ctx context.Context) (*int, error) {
func (i *Importer) Create(ctx context.Context) (*int, error) {
// create folder hierarchy and set parent folder id
path := i.Input.DirEntry().Path
path = filepath.Dir(path)
@ -223,7 +221,7 @@ func (i *fileFolderImporter) Create(ctx context.Context) (*int, error) {
return i.createFile(ctx, folder)
}
func (i *fileFolderImporter) createFile(ctx context.Context, parentFolder *file.Folder) (*int, error) {
func (i *Importer) createFile(ctx context.Context, parentFolder *models.Folder) (*int, error) {
if parentFolder != nil {
i.file.Base().ParentFolderID = parentFolder.ID
}
@ -236,7 +234,7 @@ func (i *fileFolderImporter) createFile(ctx context.Context, parentFolder *file.
return &id, nil
}
func (i *fileFolderImporter) createFolder(ctx context.Context, parentFolder *file.Folder) (*int, error) {
func (i *Importer) createFolder(ctx context.Context, parentFolder *models.Folder) (*int, error) {
if parentFolder != nil {
i.folder.ParentFolderID = &parentFolder.ID
}
@ -249,7 +247,7 @@ func (i *fileFolderImporter) createFolder(ctx context.Context, parentFolder *fil
return &id, nil
}
func (i *fileFolderImporter) Update(ctx context.Context, id int) error {
func (i *Importer) Update(ctx context.Context, id int) error {
// update not supported
return nil
}

View file

@ -11,6 +11,7 @@ import (
"time"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/txn"
)
@ -40,14 +41,14 @@ func (r folderCreatorStatRenamerImpl) Mkdir(name string, perm os.FileMode) error
type Mover struct {
Renamer DirMakerStatRenamer
Files GetterUpdater
Folders FolderStore
Files models.FileFinderUpdater
Folders models.FolderReaderWriter
moved map[string]string
foldersCreated []string
}
func NewMover(fileStore GetterUpdater, folderStore FolderStore) *Mover {
func NewMover(fileStore models.FileFinderUpdater, folderStore models.FolderReaderWriter) *Mover {
return &Mover{
Files: fileStore,
Folders: folderStore,
@ -60,7 +61,7 @@ func NewMover(fileStore GetterUpdater, folderStore FolderStore) *Mover {
// Move moves the file to the given folder and basename. If basename is empty, then the existing basename is used.
// Assumes that the parent folder exists in the filesystem.
func (m *Mover) Move(ctx context.Context, f File, folder *Folder, basename string) error {
func (m *Mover) Move(ctx context.Context, f models.File, folder *models.Folder, basename string) error {
fBase := f.Base()
// don't allow moving files in zip files

View file

@ -13,6 +13,7 @@ import (
"github.com/remeh/sizedwaitgroup"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/txn"
"github.com/stashapp/stash/pkg/utils"
)
@ -24,15 +25,6 @@ const (
maxRetries = -1
)
// Repository provides access to storage methods for files and folders.
type Repository struct {
txn.Manager
txn.DatabaseProvider
Store
FolderStore FolderStore
}
// Scanner scans files into the database.
//
// The scan process works using two goroutines. The first walks through the provided paths
@ -59,7 +51,7 @@ type Repository struct {
// If the file is not a renamed file, then the decorators are fired and the file is created, then
// the applicable handlers are fired.
type Scanner struct {
FS FS
FS models.FS
Repository Repository
FingerprintCalculator FingerprintCalculator
@ -67,6 +59,38 @@ type Scanner struct {
FileDecorators []Decorator
}
// FingerprintCalculator calculates a fingerprint for the provided file.
type FingerprintCalculator interface {
CalculateFingerprints(f *models.BaseFile, o Opener, useExisting bool) ([]models.Fingerprint, error)
}
// Decorator wraps the Decorate method to add additional functionality while scanning files.
type Decorator interface {
Decorate(ctx context.Context, fs models.FS, f models.File) (models.File, error)
IsMissingMetadata(ctx context.Context, fs models.FS, f models.File) bool
}
type FilteredDecorator struct {
Decorator
Filter
}
// Decorate runs the decorator if the filter accepts the file.
func (d *FilteredDecorator) Decorate(ctx context.Context, fs models.FS, f models.File) (models.File, error) {
if d.Accept(ctx, f) {
return d.Decorator.Decorate(ctx, fs, f)
}
return f, nil
}
func (d *FilteredDecorator) IsMissingMetadata(ctx context.Context, fs models.FS, f models.File) bool {
if d.Accept(ctx, f) {
return d.Decorator.IsMissingMetadata(ctx, fs, f)
}
return false
}
// ProgressReporter is used to report progress of the scan.
type ProgressReporter interface {
AddTotal(total int)
@ -129,8 +153,8 @@ func (s *Scanner) Scan(ctx context.Context, handlers []Handler, options ScanOpti
}
type scanFile struct {
*BaseFile
fs FS
*models.BaseFile
fs models.FS
info fs.FileInfo
}
@ -198,7 +222,7 @@ func (s *scanJob) queueFiles(ctx context.Context, paths []string) error {
return err
}
func (s *scanJob) queueFileFunc(ctx context.Context, f FS, zipFile *scanFile) fs.WalkDirFunc {
func (s *scanJob) queueFileFunc(ctx context.Context, f models.FS, zipFile *scanFile) fs.WalkDirFunc {
return func(path string, d fs.DirEntry, err error) error {
if err != nil {
// don't let errors prevent scanning
@ -229,8 +253,8 @@ func (s *scanJob) queueFileFunc(ctx context.Context, f FS, zipFile *scanFile) fs
}
ff := scanFile{
BaseFile: &BaseFile{
DirEntry: DirEntry{
BaseFile: &models.BaseFile{
DirEntry: models.DirEntry{
ModTime: modTime(info),
},
Path: path,
@ -286,7 +310,7 @@ func (s *scanJob) queueFileFunc(ctx context.Context, f FS, zipFile *scanFile) fs
}
}
func getFileSize(f FS, path string, info fs.FileInfo) (int64, error) {
func getFileSize(f models.FS, path string, info fs.FileInfo) (int64, error) {
// #2196/#3042 - replace size with target size if file is a symlink
if info.Mode()&os.ModeSymlink == os.ModeSymlink {
targetInfo, err := f.Stat(path)
@ -408,10 +432,10 @@ func (s *scanJob) processQueueItem(ctx context.Context, f scanFile) {
})
}
func (s *scanJob) getFolderID(ctx context.Context, path string) (*FolderID, error) {
func (s *scanJob) getFolderID(ctx context.Context, path string) (*models.FolderID, error) {
// check the folder cache first
if f, ok := s.folderPathToID.Load(path); ok {
v := f.(FolderID)
v := f.(models.FolderID)
return &v, nil
}
@ -428,7 +452,7 @@ func (s *scanJob) getFolderID(ctx context.Context, path string) (*FolderID, erro
return &ret.ID, nil
}
func (s *scanJob) getZipFileID(ctx context.Context, zipFile *scanFile) (*ID, error) {
func (s *scanJob) getZipFileID(ctx context.Context, zipFile *scanFile) (*models.FileID, error) {
if zipFile == nil {
return nil, nil
}
@ -441,11 +465,11 @@ func (s *scanJob) getZipFileID(ctx context.Context, zipFile *scanFile) (*ID, err
// check the folder cache first
if f, ok := s.zipPathToID.Load(path); ok {
v := f.(ID)
v := f.(models.FileID)
return &v, nil
}
ret, err := s.Repository.FindByPath(ctx, path)
ret, err := s.Repository.FileStore.FindByPath(ctx, path)
if err != nil {
return nil, fmt.Errorf("getting zip file ID for %q: %w", path, err)
}
@ -489,7 +513,7 @@ func (s *scanJob) handleFolder(ctx context.Context, file scanFile) error {
})
}
func (s *scanJob) onNewFolder(ctx context.Context, file scanFile) (*Folder, error) {
func (s *scanJob) onNewFolder(ctx context.Context, file scanFile) (*models.Folder, error) {
renamed, err := s.handleFolderRename(ctx, file)
if err != nil {
return nil, err
@ -501,7 +525,7 @@ func (s *scanJob) onNewFolder(ctx context.Context, file scanFile) (*Folder, erro
now := time.Now()
toCreate := &Folder{
toCreate := &models.Folder{
DirEntry: file.DirEntry,
Path: file.Path,
CreatedAt: now,
@ -536,7 +560,7 @@ func (s *scanJob) onNewFolder(ctx context.Context, file scanFile) (*Folder, erro
return toCreate, nil
}
func (s *scanJob) handleFolderRename(ctx context.Context, file scanFile) (*Folder, error) {
func (s *scanJob) handleFolderRename(ctx context.Context, file scanFile) (*models.Folder, error) {
// ignore folders in zip files
if file.ZipFileID != nil {
return nil, nil
@ -572,7 +596,7 @@ func (s *scanJob) handleFolderRename(ctx context.Context, file scanFile) (*Folde
return renamedFrom, nil
}
func (s *scanJob) onExistingFolder(ctx context.Context, f scanFile, existing *Folder) (*Folder, error) {
func (s *scanJob) onExistingFolder(ctx context.Context, f scanFile, existing *models.Folder) (*models.Folder, error) {
update := false
// update if mod time is changed
@ -613,12 +637,12 @@ func modTime(info fs.FileInfo) time.Time {
func (s *scanJob) handleFile(ctx context.Context, f scanFile) error {
defer s.incrementProgress(f)
var ff File
var ff models.File
// don't use a transaction to check if new or existing
if err := s.withDB(ctx, func(ctx context.Context) error {
// determine if file already exists in data store
var err error
ff, err = s.Repository.FindByPath(ctx, f.Path)
ff, err = s.Repository.FileStore.FindByPath(ctx, f.Path)
if err != nil {
return fmt.Errorf("checking for existing file %q: %w", f.Path, err)
}
@ -661,7 +685,7 @@ func (s *scanJob) isZipFile(path string) bool {
return false
}
func (s *scanJob) onNewFile(ctx context.Context, f scanFile) (File, error) {
func (s *scanJob) onNewFile(ctx context.Context, f scanFile) (models.File, error) {
now := time.Now()
baseFile := f.BaseFile
@ -716,7 +740,7 @@ func (s *scanJob) onNewFile(ctx context.Context, f scanFile) (File, error) {
// if not renamed, queue file for creation
if err := s.withTxn(ctx, func(ctx context.Context) error {
if err := s.Repository.Create(ctx, file); err != nil {
if err := s.Repository.FileStore.Create(ctx, file); err != nil {
return fmt.Errorf("creating file %q: %w", path, err)
}
@ -732,7 +756,7 @@ func (s *scanJob) onNewFile(ctx context.Context, f scanFile) (File, error) {
return file, nil
}
func (s *scanJob) fireDecorators(ctx context.Context, fs FS, f File) (File, error) {
func (s *scanJob) fireDecorators(ctx context.Context, fs models.FS, f models.File) (models.File, error) {
for _, h := range s.FileDecorators {
var err error
f, err = h.Decorate(ctx, fs, f)
@ -744,7 +768,7 @@ func (s *scanJob) fireDecorators(ctx context.Context, fs FS, f File) (File, erro
return f, nil
}
func (s *scanJob) fireHandlers(ctx context.Context, f File, oldFile File) error {
func (s *scanJob) fireHandlers(ctx context.Context, f models.File, oldFile models.File) error {
for _, h := range s.handlers {
if err := h.Handle(ctx, f, oldFile); err != nil {
return err
@ -754,7 +778,7 @@ func (s *scanJob) fireHandlers(ctx context.Context, f File, oldFile File) error
return nil
}
func (s *scanJob) calculateFingerprints(fs FS, f *BaseFile, path string, useExisting bool) (Fingerprints, error) {
func (s *scanJob) calculateFingerprints(fs models.FS, f *models.BaseFile, path string, useExisting bool) (models.Fingerprints, error) {
// only log if we're (re)calculating fingerprints
if !useExisting {
logger.Infof("Calculating fingerprints for %s ...", path)
@ -772,7 +796,7 @@ func (s *scanJob) calculateFingerprints(fs FS, f *BaseFile, path string, useExis
return fp, nil
}
func appendFileUnique(v []File, toAdd []File) []File {
func appendFileUnique(v []models.File, toAdd []models.File) []models.File {
for _, f := range toAdd {
found := false
id := f.Base().ID
@ -791,7 +815,7 @@ func appendFileUnique(v []File, toAdd []File) []File {
return v
}
func (s *scanJob) getFileFS(f *BaseFile) (FS, error) {
func (s *scanJob) getFileFS(f *models.BaseFile) (models.FS, error) {
if f.ZipFile == nil {
return s.FS, nil
}
@ -805,11 +829,11 @@ func (s *scanJob) getFileFS(f *BaseFile) (FS, error) {
return fs.OpenZip(zipPath)
}
func (s *scanJob) handleRename(ctx context.Context, f File, fp []Fingerprint) (File, error) {
var others []File
func (s *scanJob) handleRename(ctx context.Context, f models.File, fp []models.Fingerprint) (models.File, error) {
var others []models.File
for _, tfp := range fp {
thisOthers, err := s.Repository.FindByFingerprint(ctx, tfp)
thisOthers, err := s.Repository.FileStore.FindByFingerprint(ctx, tfp)
if err != nil {
return nil, fmt.Errorf("getting files by fingerprint %v: %w", tfp, err)
}
@ -817,7 +841,7 @@ func (s *scanJob) handleRename(ctx context.Context, f File, fp []Fingerprint) (F
others = appendFileUnique(others, thisOthers)
}
var missing []File
var missing []models.File
fZipID := f.Base().ZipFileID
for _, other := range others {
@ -867,7 +891,7 @@ func (s *scanJob) handleRename(ctx context.Context, f File, fp []Fingerprint) (F
fBase.Fingerprints = otherBase.Fingerprints
if err := s.withTxn(ctx, func(ctx context.Context) error {
if err := s.Repository.Update(ctx, f); err != nil {
if err := s.Repository.FileStore.Update(ctx, f); err != nil {
return fmt.Errorf("updating file for rename %q: %w", fBase.Path, err)
}
@ -889,7 +913,7 @@ func (s *scanJob) handleRename(ctx context.Context, f File, fp []Fingerprint) (F
return f, nil
}
func (s *scanJob) isHandlerRequired(ctx context.Context, f File) bool {
func (s *scanJob) isHandlerRequired(ctx context.Context, f models.File) bool {
accept := len(s.options.HandlerRequiredFilters) == 0
for _, filter := range s.options.HandlerRequiredFilters {
// accept if any filter accepts the file
@ -910,7 +934,7 @@ func (s *scanJob) isHandlerRequired(ctx context.Context, f File) bool {
// - file size
// - image format, width or height
// - video codec, audio codec, format, width, height, framerate or bitrate
func (s *scanJob) isMissingMetadata(ctx context.Context, f scanFile, existing File) bool {
func (s *scanJob) isMissingMetadata(ctx context.Context, f scanFile, existing models.File) bool {
for _, h := range s.FileDecorators {
if h.IsMissingMetadata(ctx, f.fs, existing) {
return true
@ -920,7 +944,7 @@ func (s *scanJob) isMissingMetadata(ctx context.Context, f scanFile, existing Fi
return false
}
func (s *scanJob) setMissingMetadata(ctx context.Context, f scanFile, existing File) (File, error) {
func (s *scanJob) setMissingMetadata(ctx context.Context, f scanFile, existing models.File) (models.File, error) {
path := existing.Base().Path
logger.Infof("Updating metadata for %s", path)
@ -934,7 +958,7 @@ func (s *scanJob) setMissingMetadata(ctx context.Context, f scanFile, existing F
// queue file for update
if err := s.withTxn(ctx, func(ctx context.Context) error {
if err := s.Repository.Update(ctx, existing); err != nil {
if err := s.Repository.FileStore.Update(ctx, existing); err != nil {
return fmt.Errorf("updating file %q: %w", path, err)
}
@ -946,7 +970,7 @@ func (s *scanJob) setMissingMetadata(ctx context.Context, f scanFile, existing F
return existing, nil
}
func (s *scanJob) setMissingFingerprints(ctx context.Context, f scanFile, existing File) (File, error) {
func (s *scanJob) setMissingFingerprints(ctx context.Context, f scanFile, existing models.File) (models.File, error) {
const useExisting = true
fp, err := s.calculateFingerprints(f.fs, existing.Base(), f.Path, useExisting)
if err != nil {
@ -957,7 +981,7 @@ func (s *scanJob) setMissingFingerprints(ctx context.Context, f scanFile, existi
existing.SetFingerprints(fp)
if err := s.withTxn(ctx, func(ctx context.Context) error {
if err := s.Repository.Update(ctx, existing); err != nil {
if err := s.Repository.FileStore.Update(ctx, existing); err != nil {
return fmt.Errorf("updating file %q: %w", f.Path, err)
}
@ -971,7 +995,7 @@ func (s *scanJob) setMissingFingerprints(ctx context.Context, f scanFile, existi
}
// returns a file only if it was updated
func (s *scanJob) onExistingFile(ctx context.Context, f scanFile, existing File) (File, error) {
func (s *scanJob) onExistingFile(ctx context.Context, f scanFile, existing models.File) (models.File, error) {
base := existing.Base()
path := base.Path
@ -1006,7 +1030,7 @@ func (s *scanJob) onExistingFile(ctx context.Context, f scanFile, existing File)
// queue file for update
if err := s.withTxn(ctx, func(ctx context.Context) error {
if err := s.Repository.Update(ctx, existing); err != nil {
if err := s.Repository.FileStore.Update(ctx, existing); err != nil {
return fmt.Errorf("updating file %q: %w", path, err)
}
@ -1022,21 +1046,21 @@ func (s *scanJob) onExistingFile(ctx context.Context, f scanFile, existing File)
return existing, nil
}
func (s *scanJob) removeOutdatedFingerprints(existing File, fp Fingerprints) {
func (s *scanJob) removeOutdatedFingerprints(existing models.File, fp models.Fingerprints) {
// HACK - if no MD5 fingerprint was returned, and the oshash is changed
// then remove the MD5 fingerprint
oshash := fp.For(FingerprintTypeOshash)
oshash := fp.For(models.FingerprintTypeOshash)
if oshash == nil {
return
}
existingOshash := existing.Base().Fingerprints.For(FingerprintTypeOshash)
existingOshash := existing.Base().Fingerprints.For(models.FingerprintTypeOshash)
if existingOshash == nil || *existingOshash == *oshash {
// missing oshash or same oshash - nothing to do
return
}
md5 := fp.For(FingerprintTypeMD5)
md5 := fp.For(models.FingerprintTypeMD5)
if md5 != nil {
// nothing to do
@ -1045,11 +1069,11 @@ func (s *scanJob) removeOutdatedFingerprints(existing File, fp Fingerprints) {
// oshash has changed, MD5 is missing - remove MD5 from the existing fingerprints
logger.Infof("Removing outdated checksum from %s", existing.Base().Path)
existing.Base().Fingerprints.Remove(FingerprintTypeMD5)
existing.Base().Fingerprints.Remove(models.FingerprintTypeMD5)
}
// returns a file only if it was updated
func (s *scanJob) onUnchangedFile(ctx context.Context, f scanFile, existing File) (File, error) {
func (s *scanJob) onUnchangedFile(ctx context.Context, f scanFile, existing models.File) (models.File, error) {
var err error
isMissingMetdata := s.isMissingMetadata(ctx, f, existing)

View file

@ -9,7 +9,6 @@ import (
"strings"
"github.com/asticode/go-astisub"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/txn"
@ -87,12 +86,12 @@ func getCaptionsLangFromPath(captionPath string) string {
}
type CaptionUpdater interface {
GetCaptions(ctx context.Context, fileID file.ID) ([]*models.VideoCaption, error)
UpdateCaptions(ctx context.Context, fileID file.ID, captions []*models.VideoCaption) error
GetCaptions(ctx context.Context, fileID models.FileID) ([]*models.VideoCaption, error)
UpdateCaptions(ctx context.Context, fileID models.FileID, captions []*models.VideoCaption) error
}
// associates captions to scene/s with the same basename
func AssociateCaptions(ctx context.Context, captionPath string, txnMgr txn.Manager, fqb file.Getter, w CaptionUpdater) {
func AssociateCaptions(ctx context.Context, captionPath string, txnMgr txn.Manager, fqb models.FileFinder, w CaptionUpdater) {
captionLang := getCaptionsLangFromPath(captionPath)
captionPrefix := getCaptionPrefix(captionPath)
@ -108,7 +107,7 @@ func AssociateCaptions(ctx context.Context, captionPath string, txnMgr txn.Manag
// found some files
// filter out non video files
switch f.(type) {
case *file.VideoFile:
case *models.VideoFile:
break
default:
continue
@ -143,7 +142,7 @@ func AssociateCaptions(ctx context.Context, captionPath string, txnMgr txn.Manag
}
// CleanCaptions removes non existent/accessible language codes from captions
func CleanCaptions(ctx context.Context, f *file.VideoFile, txnMgr txn.Manager, w CaptionUpdater) error {
func CleanCaptions(ctx context.Context, f *models.VideoFile, txnMgr txn.Manager, w CaptionUpdater) error {
captions, err := w.GetCaptions(ctx, f.ID)
if err != nil {
return fmt.Errorf("getting captions for file %s: %w", f.Path, err)

View file

@ -7,6 +7,7 @@ import (
"github.com/stashapp/stash/pkg/ffmpeg"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
)
// Decorator adds video specific fields to a File.
@ -14,7 +15,7 @@ type Decorator struct {
FFProbe ffmpeg.FFProbe
}
func (d *Decorator) Decorate(ctx context.Context, fs file.FS, f file.File) (file.File, error) {
func (d *Decorator) Decorate(ctx context.Context, fs models.FS, f models.File) (models.File, error) {
if d.FFProbe == "" {
return f, errors.New("ffprobe not configured")
}
@ -42,7 +43,7 @@ func (d *Decorator) Decorate(ctx context.Context, fs file.FS, f file.File) (file
interactive = true
}
return &file.VideoFile{
return &models.VideoFile{
BaseFile: base,
Format: string(container),
VideoCodec: videoFile.VideoCodec,
@ -56,13 +57,13 @@ func (d *Decorator) Decorate(ctx context.Context, fs file.FS, f file.File) (file
}, nil
}
func (d *Decorator) IsMissingMetadata(ctx context.Context, fs file.FS, f file.File) bool {
func (d *Decorator) IsMissingMetadata(ctx context.Context, fs models.FS, f models.File) bool {
const (
unsetString = "unset"
unsetNumber = -1
)
vf, ok := f.(*file.VideoFile)
vf, ok := f.(*models.VideoFile)
if !ok {
return true
}

View file

@ -1,29 +0,0 @@
package file
// VideoFile is an extension of BaseFile to represent video files.
type VideoFile struct {
*BaseFile
Format string `json:"format"`
Width int `json:"width"`
Height int `json:"height"`
Duration float64 `json:"duration"`
VideoCodec string `json:"video_codec"`
AudioCodec string `json:"audio_codec"`
FrameRate float64 `json:"frame_rate"`
BitRate int64 `json:"bitrate"`
Interactive bool `json:"interactive"`
InteractiveSpeed *int `json:"interactive_speed"`
}
func (f VideoFile) GetWidth() int {
return f.Width
}
func (f VideoFile) GetHeight() int {
return f.Height
}
func (f VideoFile) GetFormat() string {
return f.Format
}

View file

@ -6,6 +6,8 @@ import (
"os"
"path/filepath"
"sort"
"github.com/stashapp/stash/pkg/models"
)
// Modified from github.com/facebookgo/symwalk
@ -48,7 +50,7 @@ import (
//
// Note that symwalk.Walk does not terminate if there are any non-terminating loops in
// the file structure.
func walkSym(f FS, filename string, linkDirname string, walkFn fs.WalkDirFunc) error {
func walkSym(f models.FS, filename string, linkDirname string, walkFn fs.WalkDirFunc) error {
symWalkFunc := func(path string, info fs.DirEntry, err error) error {
if fname, err := filepath.Rel(filename, path); err == nil {
@ -80,7 +82,7 @@ func walkSym(f FS, filename string, linkDirname string, walkFn fs.WalkDirFunc) e
}
// symWalk extends filepath.Walk to also follow symlinks
func symWalk(fs FS, path string, walkFn fs.WalkDirFunc) error {
func symWalk(fs models.FS, path string, walkFn fs.WalkDirFunc) error {
return walkSym(fs, path, path, walkFn)
}
@ -93,7 +95,7 @@ func (d *statDirEntry) IsDir() bool { return d.info.IsDir() }
func (d *statDirEntry) Type() fs.FileMode { return d.info.Mode().Type() }
func (d *statDirEntry) Info() (fs.FileInfo, error) { return d.info, nil }
func fsWalk(f FS, root string, fn fs.WalkDirFunc) error {
func fsWalk(f models.FS, root string, fn fs.WalkDirFunc) error {
info, err := f.Lstat(root)
if err != nil {
err = fn(root, nil, err)
@ -106,7 +108,7 @@ func fsWalk(f FS, root string, fn fs.WalkDirFunc) error {
return err
}
func walkDir(f FS, path string, d fs.DirEntry, walkDirFn fs.WalkDirFunc) error {
func walkDir(f models.FS, path string, d fs.DirEntry, walkDirFn fs.WalkDirFunc) error {
if err := walkDirFn(path, d, nil); err != nil || !d.IsDir() {
if errors.Is(err, fs.SkipDir) && d.IsDir() {
// Successfully skipped directory.
@ -143,7 +145,7 @@ func walkDir(f FS, path string, d fs.DirEntry, walkDirFn fs.WalkDirFunc) error {
// readDir reads the directory named by dirname and returns
// a sorted list of directory entries.
func readDir(fs FS, dirname string) ([]fs.DirEntry, error) {
func readDir(fs models.FS, dirname string) ([]fs.DirEntry, error) {
f, err := fs.Open(dirname)
if err != nil {
return nil, err

View file

@ -10,6 +10,7 @@ import (
"path/filepath"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/xWTF/chardet"
"golang.org/x/net/html/charset"
@ -22,14 +23,14 @@ var (
)
// ZipFS is a file system backed by a zip file.
type ZipFS struct {
type zipFS struct {
*zip.Reader
zipFileCloser io.Closer
zipInfo fs.FileInfo
zipPath string
}
func newZipFS(fs FS, path string, info fs.FileInfo) (*ZipFS, error) {
func newZipFS(fs models.FS, path string, info fs.FileInfo) (*zipFS, error) {
reader, err := fs.Open(path)
if err != nil {
return nil, err
@ -85,7 +86,7 @@ func newZipFS(fs FS, path string, info fs.FileInfo) (*ZipFS, error) {
}
}
return &ZipFS{
return &zipFS{
Reader: zipReader,
zipFileCloser: reader,
zipInfo: info,
@ -93,7 +94,7 @@ func newZipFS(fs FS, path string, info fs.FileInfo) (*ZipFS, error) {
}, nil
}
func (f *ZipFS) rel(name string) (string, error) {
func (f *zipFS) rel(name string) (string, error) {
if f.zipPath == name {
return ".", nil
}
@ -110,7 +111,7 @@ func (f *ZipFS) rel(name string) (string, error) {
return relName, nil
}
func (f *ZipFS) Stat(name string) (fs.FileInfo, error) {
func (f *zipFS) Stat(name string) (fs.FileInfo, error) {
reader, err := f.Open(name)
if err != nil {
return nil, err
@ -120,15 +121,15 @@ func (f *ZipFS) Stat(name string) (fs.FileInfo, error) {
return reader.Stat()
}
func (f *ZipFS) Lstat(name string) (fs.FileInfo, error) {
func (f *zipFS) Lstat(name string) (fs.FileInfo, error) {
return f.Stat(name)
}
func (f *ZipFS) OpenZip(name string) (*ZipFS, error) {
func (f *zipFS) OpenZip(name string) (models.ZipFS, error) {
return nil, errZipFSOpenZip
}
func (f *ZipFS) IsPathCaseSensitive(path string) (bool, error) {
func (f *zipFS) IsPathCaseSensitive(path string) (bool, error) {
return true, nil
}
@ -145,7 +146,7 @@ func (f *zipReadDirFile) ReadDir(n int) ([]fs.DirEntry, error) {
return asReadDirFile.ReadDir(n)
}
func (f *ZipFS) Open(name string) (fs.ReadDirFile, error) {
func (f *zipFS) Open(name string) (fs.ReadDirFile, error) {
relName, err := f.rel(name)
if err != nil {
return nil, err
@ -161,12 +162,12 @@ func (f *ZipFS) Open(name string) (fs.ReadDirFile, error) {
}, nil
}
func (f *ZipFS) Close() error {
func (f *zipFS) Close() error {
return f.zipFileCloser.Close()
}
// openOnly returns a ReadCloser where calling Close will close the zip fs as well.
func (f *ZipFS) OpenOnly(name string) (io.ReadCloser, error) {
func (f *zipFS) OpenOnly(name string) (io.ReadCloser, error) {
r, err := f.Open(name)
if err != nil {
return nil, err

View file

@ -8,15 +8,14 @@ import (
"github.com/stashapp/stash/pkg/models/jsonschema"
)
type ChapterCreatorUpdater interface {
Create(ctx context.Context, newGalleryChapter *models.GalleryChapter) error
Update(ctx context.Context, updatedGalleryChapter *models.GalleryChapter) error
type ChapterImporterReaderWriter interface {
models.GalleryChapterCreatorUpdater
FindByGalleryID(ctx context.Context, galleryID int) ([]*models.GalleryChapter, error)
}
type ChapterImporter struct {
GalleryID int
ReaderWriter ChapterCreatorUpdater
ReaderWriter ChapterImporterReaderWriter
Input jsonschema.GalleryChapter
MissingRefBehaviour models.ImportMissingRefEnum

View file

@ -41,12 +41,7 @@ func (s *Service) Destroy(ctx context.Context, i *models.Gallery, fileDeleter *i
return imgsDestroyed, nil
}
type ChapterDestroyer interface {
FindByGalleryID(ctx context.Context, galleryID int) ([]*models.GalleryChapter, error)
Destroy(ctx context.Context, id int) error
}
func DestroyChapter(ctx context.Context, galleryChapter *models.GalleryChapter, qb ChapterDestroyer) error {
func DestroyChapter(ctx context.Context, galleryChapter *models.GalleryChapter, qb models.GalleryChapterDestroyer) error {
return qb.Destroy(ctx, galleryChapter.ID)
}

View file

@ -7,13 +7,8 @@ import (
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/json"
"github.com/stashapp/stash/pkg/models/jsonschema"
"github.com/stashapp/stash/pkg/studio"
)
type ChapterFinder interface {
FindByGalleryID(ctx context.Context, galleryID int) ([]*models.GalleryChapter, error)
}
// ToBasicJSON converts a gallery object into its JSON object equivalent. It
// does not convert the relationships to other objects.
func ToBasicJSON(gallery *models.Gallery) (*jsonschema.Gallery, error) {
@ -48,7 +43,7 @@ func ToBasicJSON(gallery *models.Gallery) (*jsonschema.Gallery, error) {
// GetStudioName returns the name of the provided gallery's studio. It returns an
// empty string if there is no studio assigned to the gallery.
func GetStudioName(ctx context.Context, reader studio.Finder, gallery *models.Gallery) (string, error) {
func GetStudioName(ctx context.Context, reader models.StudioGetter, gallery *models.Gallery) (string, error) {
if gallery.StudioID != nil {
studio, err := reader.Find(ctx, *gallery.StudioID)
if err != nil {
@ -65,7 +60,7 @@ func GetStudioName(ctx context.Context, reader studio.Finder, gallery *models.Ga
// GetGalleryChaptersJSON returns a slice of GalleryChapter JSON representation
// objects corresponding to the provided gallery's chapters.
func GetGalleryChaptersJSON(ctx context.Context, chapterReader ChapterFinder, gallery *models.Gallery) ([]jsonschema.GalleryChapter, error) {
func GetGalleryChaptersJSON(ctx context.Context, chapterReader models.GalleryChapterFinder, gallery *models.Gallery) ([]jsonschema.GalleryChapter, error) {
galleryChapters, err := chapterReader.FindByGalleryID(ctx, gallery.ID)
if err != nil {
return nil, fmt.Errorf("error getting gallery chapters: %v", err)

View file

@ -3,7 +3,6 @@ package gallery
import (
"errors"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/json"
"github.com/stashapp/stash/pkg/models/jsonschema"
@ -50,8 +49,8 @@ var (
func createFullGallery(id int) models.Gallery {
return models.Gallery{
ID: id,
Files: models.NewRelatedFiles([]file.File{
&file.BaseFile{
Files: models.NewRelatedFiles([]models.File{
&models.BaseFile{
Path: path,
},
}),
@ -69,8 +68,8 @@ func createFullGallery(id int) models.Gallery {
func createEmptyGallery(id int) models.Gallery {
return models.Gallery{
ID: id,
Files: models.NewRelatedFiles([]file.File{
&file.BaseFile{
Files: models.NewRelatedFiles([]models.File{
&models.BaseFile{
Path: path,
},
}),

View file

@ -5,22 +5,25 @@ import (
"fmt"
"strings"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/jsonschema"
"github.com/stashapp/stash/pkg/performer"
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
"github.com/stashapp/stash/pkg/studio"
"github.com/stashapp/stash/pkg/tag"
)
type ImporterReaderWriter interface {
models.GalleryCreatorUpdater
FindByFileID(ctx context.Context, fileID models.FileID) ([]*models.Gallery, error)
FindByFolderID(ctx context.Context, folderID models.FolderID) ([]*models.Gallery, error)
FindUserGalleryByTitle(ctx context.Context, title string) ([]*models.Gallery, error)
}
type Importer struct {
ReaderWriter FullCreatorUpdater
StudioWriter studio.NameFinderCreator
PerformerWriter performer.NameFinderCreator
TagWriter tag.NameFinderCreator
FileFinder file.Getter
FolderFinder file.FolderGetter
ReaderWriter ImporterReaderWriter
StudioWriter models.StudioFinderCreator
PerformerWriter models.PerformerFinderCreator
TagWriter models.TagFinderCreator
FileFinder models.FileFinder
FolderFinder models.FolderFinder
Input jsonschema.Gallery
MissingRefBehaviour models.ImportMissingRefEnum
@ -28,11 +31,6 @@ type Importer struct {
gallery models.Gallery
}
type FullCreatorUpdater interface {
FinderCreatorUpdater
Update(ctx context.Context, updatedGallery *models.Gallery) error
}
func (i *Importer) PreImport(ctx context.Context) error {
i.gallery = i.galleryJSONToGallery(i.Input)
@ -251,7 +249,7 @@ func (i *Importer) createTags(ctx context.Context, names []string) ([]*models.Ta
}
func (i *Importer) populateFilesFolder(ctx context.Context) error {
files := make([]file.File, 0)
files := make([]models.File, 0)
for _, ref := range i.Input.ZipFiles {
path := ref
@ -340,7 +338,7 @@ func (i *Importer) FindExistingID(ctx context.Context) (*int, error) {
}
func (i *Importer) Create(ctx context.Context) (*int, error) {
var fileIDs []file.ID
var fileIDs []models.FileID
for _, f := range i.gallery.Files.List() {
fileIDs = append(fileIDs, f.Base().ID)
}

View file

@ -6,7 +6,6 @@ import (
"testing"
"time"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/json"
"github.com/stashapp/stash/pkg/models/jsonschema"
@ -68,7 +67,7 @@ func TestImporterPreImport(t *testing.T) {
Rating: &rating,
Organized: organized,
URL: url,
Files: models.NewRelatedFiles([]file.File{}),
Files: models.NewRelatedFiles([]models.File{}),
TagIDs: models.NewRelatedIDs([]int{}),
PerformerIDs: models.NewRelatedIDs([]int{}),
CreatedAt: createdAt,

View file

@ -4,27 +4,10 @@ import (
"context"
"strconv"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
)
type Queryer interface {
Query(ctx context.Context, galleryFilter *models.GalleryFilterType, findFilter *models.FindFilterType) ([]*models.Gallery, int, error)
}
type CountQueryer interface {
QueryCount(ctx context.Context, galleryFilter *models.GalleryFilterType, findFilter *models.FindFilterType) (int, error)
}
type Finder interface {
FindByPath(ctx context.Context, p string) ([]*models.Gallery, error)
FindUserGalleryByTitle(ctx context.Context, title string) ([]*models.Gallery, error)
FindByFolderID(ctx context.Context, folderID file.FolderID) ([]*models.Gallery, error)
FindByFileID(ctx context.Context, fileID file.ID) ([]*models.Gallery, error)
FindByFingerprints(ctx context.Context, fp []file.Fingerprint) ([]*models.Gallery, error)
}
func CountByPerformerID(ctx context.Context, r CountQueryer, id int) (int, error) {
func CountByPerformerID(ctx context.Context, r models.GalleryQueryer, id int) (int, error) {
filter := &models.GalleryFilterType{
Performers: &models.MultiCriterionInput{
Value: []string{strconv.Itoa(id)},
@ -35,7 +18,7 @@ func CountByPerformerID(ctx context.Context, r CountQueryer, id int) (int, error
return r.QueryCount(ctx, filter, nil)
}
func CountByStudioID(ctx context.Context, r CountQueryer, id int, depth *int) (int, error) {
func CountByStudioID(ctx context.Context, r models.GalleryQueryer, id int, depth *int) (int, error) {
filter := &models.GalleryFilterType{
Studios: &models.HierarchicalMultiCriterionInput{
Value: []string{strconv.Itoa(id)},
@ -47,7 +30,7 @@ func CountByStudioID(ctx context.Context, r CountQueryer, id int, depth *int) (i
return r.QueryCount(ctx, filter, nil)
}
func CountByTagID(ctx context.Context, r CountQueryer, id int, depth *int) (int, error) {
func CountByTagID(ctx context.Context, r models.GalleryQueryer, id int, depth *int) (int, error) {
filter := &models.GalleryFilterType{
Tags: &models.HierarchicalMultiCriterionInput{
Value: []string{strconv.Itoa(id)},

View file

@ -7,39 +7,40 @@ import (
"strings"
"time"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/plugin"
)
type FinderCreatorUpdater interface {
Finder
Create(ctx context.Context, newGallery *models.Gallery, fileIDs []file.ID) error
type ScanCreatorUpdater interface {
FindByFileID(ctx context.Context, fileID models.FileID) ([]*models.Gallery, error)
FindByFingerprints(ctx context.Context, fp []models.Fingerprint) ([]*models.Gallery, error)
GetFiles(ctx context.Context, relatedID int) ([]models.File, error)
Create(ctx context.Context, newGallery *models.Gallery, fileIDs []models.FileID) error
UpdatePartial(ctx context.Context, id int, updatedGallery models.GalleryPartial) (*models.Gallery, error)
AddFileID(ctx context.Context, id int, fileID file.ID) error
models.FileLoader
AddFileID(ctx context.Context, id int, fileID models.FileID) error
}
type SceneFinderUpdater interface {
type ScanSceneFinderUpdater interface {
FindByPath(ctx context.Context, p string) ([]*models.Scene, error)
Update(ctx context.Context, updatedScene *models.Scene) error
AddGalleryIDs(ctx context.Context, sceneID int, galleryIDs []int) error
}
type ImageFinderUpdater interface {
FindByZipFileID(ctx context.Context, zipFileID file.ID) ([]*models.Image, error)
type ScanImageFinderUpdater interface {
FindByZipFileID(ctx context.Context, zipFileID models.FileID) ([]*models.Image, error)
UpdatePartial(ctx context.Context, id int, partial models.ImagePartial) (*models.Image, error)
}
type ScanHandler struct {
CreatorUpdater FullCreatorUpdater
SceneFinderUpdater SceneFinderUpdater
ImageFinderUpdater ImageFinderUpdater
CreatorUpdater ScanCreatorUpdater
SceneFinderUpdater ScanSceneFinderUpdater
ImageFinderUpdater ScanImageFinderUpdater
PluginCache *plugin.Cache
}
func (h *ScanHandler) Handle(ctx context.Context, f file.File, oldFile file.File) error {
func (h *ScanHandler) Handle(ctx context.Context, f models.File, oldFile models.File) error {
baseFile := f.Base()
// try to match the file to a gallery
@ -83,7 +84,7 @@ func (h *ScanHandler) Handle(ctx context.Context, f file.File, oldFile file.File
logger.Infof("%s doesn't exist. Creating new gallery...", f.Base().Path)
if err := h.CreatorUpdater.Create(ctx, newGallery, []file.ID{baseFile.ID}); err != nil {
if err := h.CreatorUpdater.Create(ctx, newGallery, []models.FileID{baseFile.ID}); err != nil {
return fmt.Errorf("creating new gallery: %w", err)
}
@ -112,7 +113,7 @@ func (h *ScanHandler) Handle(ctx context.Context, f file.File, oldFile file.File
return nil
}
func (h *ScanHandler) associateExisting(ctx context.Context, existing []*models.Gallery, f file.File, updateExisting bool) error {
func (h *ScanHandler) associateExisting(ctx context.Context, existing []*models.Gallery, f models.File, updateExisting bool) error {
for _, i := range existing {
if err := i.LoadFiles(ctx, h.CreatorUpdater); err != nil {
return err
@ -146,7 +147,7 @@ func (h *ScanHandler) associateExisting(ctx context.Context, existing []*models.
return nil
}
func (h *ScanHandler) associateScene(ctx context.Context, existing []*models.Gallery, f file.File) error {
func (h *ScanHandler) associateScene(ctx context.Context, existing []*models.Gallery, f models.File) error {
galleryIDs := make([]int, len(existing))
for i, g := range existing {
galleryIDs[i] = g.ID

View file

@ -3,50 +3,25 @@ package gallery
import (
"context"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/image"
"github.com/stashapp/stash/pkg/models"
)
type FinderByFile interface {
FindByFileID(ctx context.Context, fileID file.ID) ([]*models.Gallery, error)
}
type Repository interface {
models.GalleryFinder
FinderByFile
Destroy(ctx context.Context, id int) error
models.FileLoader
ImageUpdater
PartialUpdater
}
type PartialUpdater interface {
UpdatePartial(ctx context.Context, id int, updatedGallery models.GalleryPartial) (*models.Gallery, error)
}
type ImageFinder interface {
FindByFolderID(ctx context.Context, folder file.FolderID) ([]*models.Image, error)
FindByZipFileID(ctx context.Context, zipFileID file.ID) ([]*models.Image, error)
FindByFolderID(ctx context.Context, folder models.FolderID) ([]*models.Image, error)
FindByZipFileID(ctx context.Context, zipFileID models.FileID) ([]*models.Image, error)
models.GalleryIDLoader
}
type ImageService interface {
Destroy(ctx context.Context, i *models.Image, fileDeleter *image.FileDeleter, deleteGenerated, deleteFile bool) error
DestroyZipImages(ctx context.Context, zipFile file.File, fileDeleter *image.FileDeleter, deleteGenerated bool) ([]*models.Image, error)
}
type ChapterRepository interface {
ChapterFinder
ChapterDestroyer
Update(ctx context.Context, updatedObject models.GalleryChapter) (*models.GalleryChapter, error)
DestroyZipImages(ctx context.Context, zipFile models.File, fileDeleter *image.FileDeleter, deleteGenerated bool) ([]*models.Image, error)
}
type Service struct {
Repository Repository
Repository models.GalleryReaderWriter
ImageFinder ImageFinder
ImageService ImageService
File file.Store
Folder file.FolderStore
File models.FileReaderWriter
Folder models.FolderReaderWriter
}

View file

@ -54,7 +54,7 @@ func (s *Service) RemoveImages(ctx context.Context, g *models.Gallery, toRemove
return s.Updated(ctx, g.ID)
}
func AddPerformer(ctx context.Context, qb PartialUpdater, o *models.Gallery, performerID int) error {
func AddPerformer(ctx context.Context, qb models.GalleryUpdater, o *models.Gallery, performerID int) error {
_, err := qb.UpdatePartial(ctx, o.ID, models.GalleryPartial{
PerformerIDs: &models.UpdateIDs{
IDs: []int{performerID},
@ -64,7 +64,7 @@ func AddPerformer(ctx context.Context, qb PartialUpdater, o *models.Gallery, per
return err
}
func AddTag(ctx context.Context, qb PartialUpdater, o *models.Gallery, tagID int) error {
func AddTag(ctx context.Context, qb models.GalleryUpdater, o *models.Gallery, tagID int) error {
_, err := qb.UpdatePartial(ctx, o.ID, models.GalleryPartial{
TagIDs: &models.UpdateIDs{
IDs: []int{tagID},

View file

@ -13,8 +13,8 @@ import (
"github.com/stashapp/stash/pkg/ffmpeg"
"github.com/stashapp/stash/pkg/ffmpeg/transcoder"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
)
const (
@ -23,7 +23,7 @@ const (
rows = 5
)
func Generate(encoder *ffmpeg.FFMpeg, videoFile *file.VideoFile) (*uint64, error) {
func Generate(encoder *ffmpeg.FFMpeg, videoFile *models.VideoFile) (*uint64, error) {
sprite, err := generateSprite(encoder, videoFile)
if err != nil {
return nil, err
@ -76,7 +76,7 @@ func combineImages(images []image.Image) image.Image {
return montage
}
func generateSprite(encoder *ffmpeg.FFMpeg, videoFile *file.VideoFile) (image.Image, error) {
func generateSprite(encoder *ffmpeg.FFMpeg, videoFile *models.VideoFile) (image.Image, error) {
logger.Infof("[generator] generating phash sprite for %s", videoFile.Path)
// Generate sprite image offset by 5% on each end to avoid intro/outros

View file

@ -10,10 +10,6 @@ import (
"github.com/stashapp/stash/pkg/models/paths"
)
type Destroyer interface {
Destroy(ctx context.Context, id int) error
}
// FileDeleter is an extension of file.Deleter that handles deletion of image files.
type FileDeleter struct {
*file.Deleter
@ -45,7 +41,7 @@ func (s *Service) Destroy(ctx context.Context, i *models.Image, fileDeleter *Fil
// DestroyZipImages destroys all images in zip, optionally marking the files and generated files for deletion.
// Returns a slice of images that were destroyed.
func (s *Service) DestroyZipImages(ctx context.Context, zipFile file.File, fileDeleter *FileDeleter, deleteGenerated bool) ([]*models.Image, error) {
func (s *Service) DestroyZipImages(ctx context.Context, zipFile models.File, fileDeleter *FileDeleter, deleteGenerated bool) ([]*models.Image, error) {
var imgsDestroyed []*models.Image
imgs, err := s.Repository.FindByZipFileID(ctx, zipFile.Base().ID)

View file

@ -6,7 +6,6 @@ import (
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/json"
"github.com/stashapp/stash/pkg/models/jsonschema"
"github.com/stashapp/stash/pkg/studio"
)
// ToBasicJSON converts a image object into its JSON object equivalent. It
@ -53,7 +52,7 @@ func ToBasicJSON(image *models.Image) *jsonschema.Image {
// GetStudioName returns the name of the provided image's studio. It returns an
// empty string if there is no studio assigned to the image.
func GetStudioName(ctx context.Context, reader studio.Finder, image *models.Image) (string, error) {
func GetStudioName(ctx context.Context, reader models.StudioGetter, image *models.Image) (string, error) {
if image.StudioID != nil {
studio, err := reader.Find(ctx, *image.StudioID)
if err != nil {

View file

@ -3,7 +3,6 @@ package image
import (
"errors"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/json"
"github.com/stashapp/stash/pkg/models/jsonschema"
@ -45,8 +44,8 @@ var (
func createFullImage(id int) models.Image {
return models.Image{
ID: id,
Files: models.NewRelatedFiles([]file.File{
&file.BaseFile{
Files: models.NewRelatedFiles([]models.File{
&models.BaseFile{
Path: path,
},
}),

View file

@ -5,13 +5,9 @@ import (
"fmt"
"strings"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/jsonschema"
"github.com/stashapp/stash/pkg/performer"
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
"github.com/stashapp/stash/pkg/studio"
"github.com/stashapp/stash/pkg/tag"
)
type GalleryFinder interface {
@ -19,18 +15,18 @@ type GalleryFinder interface {
FindUserGalleryByTitle(ctx context.Context, title string) ([]*models.Gallery, error)
}
type FullCreatorUpdater interface {
FinderCreatorUpdater
Update(ctx context.Context, updatedImage *models.Image) error
type ImporterReaderWriter interface {
models.ImageCreatorUpdater
FindByFileID(ctx context.Context, fileID models.FileID) ([]*models.Image, error)
}
type Importer struct {
ReaderWriter FullCreatorUpdater
FileFinder file.Getter
StudioWriter studio.NameFinderCreator
ReaderWriter ImporterReaderWriter
FileFinder models.FileFinder
StudioWriter models.StudioFinderCreator
GalleryFinder GalleryFinder
PerformerWriter performer.NameFinderCreator
TagWriter tag.NameFinderCreator
PerformerWriter models.PerformerFinderCreator
TagWriter models.TagFinderCreator
Input jsonschema.Image
MissingRefBehaviour models.ImportMissingRefEnum
@ -99,7 +95,7 @@ func (i *Importer) imageJSONToImage(imageJSON jsonschema.Image) models.Image {
}
func (i *Importer) populateFiles(ctx context.Context) error {
files := make([]file.File, 0)
files := make([]models.File, 0)
for _, ref := range i.Input.Files {
path := ref
@ -330,7 +326,7 @@ func (i *Importer) FindExistingID(ctx context.Context) (*int, error) {
}
func (i *Importer) Create(ctx context.Context) (*int, error) {
var fileIDs []file.ID
var fileIDs []models.FileID
for _, f := range i.image.Files.List() {
fileIDs = append(fileIDs, f.Base().ID)
}
@ -360,7 +356,7 @@ func (i *Importer) Update(ctx context.Context, id int) error {
return nil
}
func importTags(ctx context.Context, tagWriter tag.NameFinderCreator, names []string, missingRefBehaviour models.ImportMissingRefEnum) ([]*models.Tag, error) {
func importTags(ctx context.Context, tagWriter models.TagFinderCreator, names []string, missingRefBehaviour models.ImportMissingRefEnum) ([]*models.Tag, error) {
tags, err := tagWriter.FindByNames(ctx, names, false)
if err != nil {
return nil, err
@ -395,7 +391,7 @@ func importTags(ctx context.Context, tagWriter tag.NameFinderCreator, names []st
return tags, nil
}
func createTags(ctx context.Context, tagWriter tag.NameFinderCreator, names []string) ([]*models.Tag, error) {
func createTags(ctx context.Context, tagWriter models.TagCreator, names []string) ([]*models.Tag, error) {
var ret []*models.Tag
for _, name := range names {
newTag := models.NewTag(name)

View file

@ -7,14 +7,6 @@ import (
"github.com/stashapp/stash/pkg/models"
)
type Queryer interface {
Query(ctx context.Context, options models.ImageQueryOptions) (*models.ImageQueryResult, error)
}
type CountQueryer interface {
QueryCount(ctx context.Context, imageFilter *models.ImageFilterType, findFilter *models.FindFilterType) (int, error)
}
// QueryOptions returns a ImageQueryResult populated with the provided filters.
func QueryOptions(imageFilter *models.ImageFilterType, findFilter *models.FindFilterType, count bool) models.ImageQueryOptions {
return models.ImageQueryOptions{
@ -27,7 +19,7 @@ func QueryOptions(imageFilter *models.ImageFilterType, findFilter *models.FindFi
}
// Query queries for images using the provided filters.
func Query(ctx context.Context, qb Queryer, imageFilter *models.ImageFilterType, findFilter *models.FindFilterType) ([]*models.Image, error) {
func Query(ctx context.Context, qb models.ImageQueryer, imageFilter *models.ImageFilterType, findFilter *models.FindFilterType) ([]*models.Image, error) {
result, err := qb.Query(ctx, QueryOptions(imageFilter, findFilter, false))
if err != nil {
return nil, err
@ -41,7 +33,7 @@ func Query(ctx context.Context, qb Queryer, imageFilter *models.ImageFilterType,
return images, nil
}
func CountByPerformerID(ctx context.Context, r CountQueryer, id int) (int, error) {
func CountByPerformerID(ctx context.Context, r models.ImageQueryer, id int) (int, error) {
filter := &models.ImageFilterType{
Performers: &models.MultiCriterionInput{
Value: []string{strconv.Itoa(id)},
@ -52,7 +44,7 @@ func CountByPerformerID(ctx context.Context, r CountQueryer, id int) (int, error
return r.QueryCount(ctx, filter, nil)
}
func CountByStudioID(ctx context.Context, r CountQueryer, id int, depth *int) (int, error) {
func CountByStudioID(ctx context.Context, r models.ImageQueryer, id int, depth *int) (int, error) {
filter := &models.ImageFilterType{
Studios: &models.HierarchicalMultiCriterionInput{
Value: []string{strconv.Itoa(id)},
@ -64,7 +56,7 @@ func CountByStudioID(ctx context.Context, r CountQueryer, id int, depth *int) (i
return r.QueryCount(ctx, filter, nil)
}
func CountByTagID(ctx context.Context, r CountQueryer, id int, depth *int) (int, error) {
func CountByTagID(ctx context.Context, r models.ImageQueryer, id int, depth *int) (int, error) {
filter := &models.ImageFilterType{
Tags: &models.HierarchicalMultiCriterionInput{
Value: []string{strconv.Itoa(id)},
@ -76,7 +68,7 @@ func CountByTagID(ctx context.Context, r CountQueryer, id int, depth *int) (int,
return r.QueryCount(ctx, filter, nil)
}
func FindByGalleryID(ctx context.Context, r Queryer, galleryID int, sortBy string, sortDir models.SortDirectionEnum) ([]*models.Image, error) {
func FindByGalleryID(ctx context.Context, r models.ImageQueryer, galleryID int, sortBy string, sortDir models.SortDirectionEnum) ([]*models.Image, error) {
perPage := -1
findFilter := models.FindFilterType{
@ -99,7 +91,7 @@ func FindByGalleryID(ctx context.Context, r Queryer, galleryID int, sortBy strin
}, &findFilter)
}
func FindGalleryCover(ctx context.Context, r Queryer, galleryID int, galleryCoverRegex string) (*models.Image, error) {
func FindGalleryCover(ctx context.Context, r models.ImageQueryer, galleryID int, galleryCoverRegex string) (*models.Image, error) {
const useCoverJpg = true
img, err := findGalleryCover(ctx, r, galleryID, useCoverJpg, galleryCoverRegex)
if err != nil {
@ -114,7 +106,7 @@ func FindGalleryCover(ctx context.Context, r Queryer, galleryID int, galleryCove
return findGalleryCover(ctx, r, galleryID, !useCoverJpg, galleryCoverRegex)
}
func findGalleryCover(ctx context.Context, r Queryer, galleryID int, useCoverJpg bool, galleryCoverRegex string) (*models.Image, error) {
func findGalleryCover(ctx context.Context, r models.ImageQueryer, galleryID int, useCoverJpg bool, galleryCoverRegex string) (*models.Image, error) {
// try to find cover.jpg in the gallery
perPage := 1
sortBy := "path"

View file

@ -8,7 +8,6 @@ import (
"path/filepath"
"time"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/paths"
@ -21,21 +20,22 @@ var (
ErrNotImageFile = errors.New("not an image file")
)
type FinderCreatorUpdater interface {
FindByFileID(ctx context.Context, fileID file.ID) ([]*models.Image, error)
FindByFolderID(ctx context.Context, folderID file.FolderID) ([]*models.Image, error)
FindByFingerprints(ctx context.Context, fp []file.Fingerprint) ([]*models.Image, error)
type ScanCreatorUpdater interface {
FindByFileID(ctx context.Context, fileID models.FileID) ([]*models.Image, error)
FindByFolderID(ctx context.Context, folderID models.FolderID) ([]*models.Image, error)
FindByFingerprints(ctx context.Context, fp []models.Fingerprint) ([]*models.Image, error)
GetFiles(ctx context.Context, relatedID int) ([]models.File, error)
GetGalleryIDs(ctx context.Context, relatedID int) ([]int, error)
Create(ctx context.Context, newImage *models.ImageCreateInput) error
UpdatePartial(ctx context.Context, id int, updatedImage models.ImagePartial) (*models.Image, error)
AddFileID(ctx context.Context, id int, fileID file.ID) error
models.GalleryIDLoader
models.FileLoader
AddFileID(ctx context.Context, id int, fileID models.FileID) error
}
type GalleryFinderCreator interface {
FindByFileID(ctx context.Context, fileID file.ID) ([]*models.Gallery, error)
FindByFolderID(ctx context.Context, folderID file.FolderID) ([]*models.Gallery, error)
Create(ctx context.Context, newObject *models.Gallery, fileIDs []file.ID) error
FindByFileID(ctx context.Context, fileID models.FileID) ([]*models.Gallery, error)
FindByFolderID(ctx context.Context, folderID models.FolderID) ([]*models.Gallery, error)
Create(ctx context.Context, newObject *models.Gallery, fileIDs []models.FileID) error
UpdatePartial(ctx context.Context, id int, updatedGallery models.GalleryPartial) (*models.Gallery, error)
}
@ -44,11 +44,11 @@ type ScanConfig interface {
}
type ScanGenerator interface {
Generate(ctx context.Context, i *models.Image, f file.File) error
Generate(ctx context.Context, i *models.Image, f models.File) error
}
type ScanHandler struct {
CreatorUpdater FinderCreatorUpdater
CreatorUpdater ScanCreatorUpdater
GalleryFinder GalleryFinderCreator
ScanGenerator ScanGenerator
@ -80,7 +80,7 @@ func (h *ScanHandler) validate() error {
return nil
}
func (h *ScanHandler) Handle(ctx context.Context, f file.File, oldFile file.File) error {
func (h *ScanHandler) Handle(ctx context.Context, f models.File, oldFile models.File) error {
if err := h.validate(); err != nil {
return err
}
@ -130,7 +130,7 @@ func (h *ScanHandler) Handle(ctx context.Context, f file.File, oldFile file.File
if err := h.CreatorUpdater.Create(ctx, &models.ImageCreateInput{
Image: newImage,
FileIDs: []file.ID{imageFile.ID},
FileIDs: []models.FileID{imageFile.ID},
}); err != nil {
return fmt.Errorf("creating new image: %w", err)
}
@ -151,8 +151,8 @@ func (h *ScanHandler) Handle(ctx context.Context, f file.File, oldFile file.File
// remove the old thumbnail if the checksum changed - we'll regenerate it
if oldFile != nil {
oldHash := oldFile.Base().Fingerprints.GetString(file.FingerprintTypeMD5)
newHash := f.Base().Fingerprints.GetString(file.FingerprintTypeMD5)
oldHash := oldFile.Base().Fingerprints.GetString(models.FingerprintTypeMD5)
newHash := f.Base().Fingerprints.GetString(models.FingerprintTypeMD5)
if oldHash != "" && newHash != "" && oldHash != newHash {
// remove cache dir of gallery
@ -173,7 +173,7 @@ func (h *ScanHandler) Handle(ctx context.Context, f file.File, oldFile file.File
return nil
}
func (h *ScanHandler) associateExisting(ctx context.Context, existing []*models.Image, f *file.BaseFile, updateExisting bool) error {
func (h *ScanHandler) associateExisting(ctx context.Context, existing []*models.Image, f *models.BaseFile, updateExisting bool) error {
for _, i := range existing {
if err := i.LoadFiles(ctx, h.CreatorUpdater); err != nil {
return err
@ -239,7 +239,7 @@ func (h *ScanHandler) associateExisting(ctx context.Context, existing []*models.
return nil
}
func (h *ScanHandler) getOrCreateFolderBasedGallery(ctx context.Context, f file.File) (*models.Gallery, error) {
func (h *ScanHandler) getOrCreateFolderBasedGallery(ctx context.Context, f models.File) (*models.Gallery, error) {
folderID := f.Base().ParentFolderID
g, err := h.GalleryFinder.FindByFolderID(ctx, folderID)
if err != nil {
@ -299,7 +299,7 @@ func (h *ScanHandler) associateFolderImages(ctx context.Context, g *models.Galle
return nil
}
func (h *ScanHandler) getOrCreateZipBasedGallery(ctx context.Context, zipFile file.File) (*models.Gallery, error) {
func (h *ScanHandler) getOrCreateZipBasedGallery(ctx context.Context, zipFile models.File) (*models.Gallery, error) {
g, err := h.GalleryFinder.FindByFileID(ctx, zipFile.Base().ID)
if err != nil {
return nil, fmt.Errorf("finding zip based gallery: %w", err)
@ -319,7 +319,7 @@ func (h *ScanHandler) getOrCreateZipBasedGallery(ctx context.Context, zipFile fi
logger.Infof("%s doesn't exist. Creating new gallery...", zipFile.Base().Path)
if err := h.GalleryFinder.Create(ctx, newGallery, []file.ID{zipFile.Base().ID}); err != nil {
if err := h.GalleryFinder.Create(ctx, newGallery, []models.FileID{zipFile.Base().ID}); err != nil {
return nil, fmt.Errorf("creating zip-based gallery: %w", err)
}
@ -328,7 +328,7 @@ func (h *ScanHandler) getOrCreateZipBasedGallery(ctx context.Context, zipFile fi
return newGallery, nil
}
func (h *ScanHandler) getOrCreateGallery(ctx context.Context, f file.File) (*models.Gallery, error) {
func (h *ScanHandler) getOrCreateGallery(ctx context.Context, f models.File) (*models.Gallery, error) {
// don't create folder-based galleries for files in zip file
if f.Base().ZipFile != nil {
return h.getOrCreateZipBasedGallery(ctx, f.Base().ZipFile)
@ -357,7 +357,7 @@ func (h *ScanHandler) getOrCreateGallery(ctx context.Context, f file.File) (*mod
return nil, nil
}
func (h *ScanHandler) getGalleryToAssociate(ctx context.Context, newImage *models.Image, f file.File) (*models.Gallery, error) {
func (h *ScanHandler) getGalleryToAssociate(ctx context.Context, newImage *models.Image, f models.File) (*models.Gallery, error) {
g, err := h.getOrCreateGallery(ctx, f)
if err != nil {
return nil, err

View file

@ -1,24 +1,10 @@
package image
import (
"context"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
)
type FinderByFile interface {
FindByFileID(ctx context.Context, fileID file.ID) ([]*models.Image, error)
FindByZipFileID(ctx context.Context, zipFileID file.ID) ([]*models.Image, error)
}
type Repository interface {
FinderByFile
Destroyer
models.FileLoader
}
type Service struct {
File file.Store
Repository Repository
File models.FileReaderWriter
Repository models.ImageReaderWriter
}

View file

@ -14,6 +14,7 @@ import (
"github.com/stashapp/stash/pkg/ffmpeg/transcoder"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/models"
)
const ffmpegImageQuality = 5
@ -68,7 +69,7 @@ func NewThumbnailEncoder(ffmpegEncoder *ffmpeg.FFMpeg, ffProbe ffmpeg.FFProbe, c
// the provided max size. It resizes based on the largest X/Y direction.
// It returns nil and an error if an error occurs reading, decoding or encoding
// the image, or if the image is not suitable for thumbnails.
func (e *ThumbnailEncoder) GetThumbnail(f file.File, maxSize int) ([]byte, error) {
func (e *ThumbnailEncoder) GetThumbnail(f models.File, maxSize int) ([]byte, error) {
reader, err := f.Open(&file.OsFS{})
if err != nil {
return nil, err
@ -82,7 +83,7 @@ func (e *ThumbnailEncoder) GetThumbnail(f file.File, maxSize int) ([]byte, error
data := buf.Bytes()
if imageFile, ok := f.(*file.ImageFile); ok {
if imageFile, ok := f.(*models.ImageFile); ok {
format := imageFile.Format
animated := imageFile.Format == formatGif
@ -98,7 +99,7 @@ func (e *ThumbnailEncoder) GetThumbnail(f file.File, maxSize int) ([]byte, error
}
// Videofiles can only be thumbnailed with ffmpeg
if _, ok := f.(*file.VideoFile); ok {
if _, ok := f.(*models.VideoFile); ok {
return e.ffmpegImageThumbnail(buf, maxSize)
}

View file

@ -6,11 +6,7 @@ import (
"github.com/stashapp/stash/pkg/models"
)
type PartialUpdater interface {
UpdatePartial(ctx context.Context, id int, partial models.ImagePartial) (*models.Image, error)
}
func AddPerformer(ctx context.Context, qb PartialUpdater, i *models.Image, performerID int) error {
func AddPerformer(ctx context.Context, qb models.ImageUpdater, i *models.Image, performerID int) error {
_, err := qb.UpdatePartial(ctx, i.ID, models.ImagePartial{
PerformerIDs: &models.UpdateIDs{
IDs: []int{performerID},
@ -21,7 +17,7 @@ func AddPerformer(ctx context.Context, qb PartialUpdater, i *models.Image, perfo
return err
}
func AddTag(ctx context.Context, qb PartialUpdater, i *models.Image, tagID int) error {
func AddTag(ctx context.Context, qb models.ImageUpdater, i *models.Image, tagID int) error {
_, err := qb.UpdatePartial(ctx, i.ID, models.ImagePartial{
TagIDs: &models.UpdateIDs{
IDs: []int{tagID},

View file

@ -20,7 +20,7 @@ type Cache struct {
// against. This means that performers with single-letter words in their names could potentially
// be missed.
// This query is expensive, so it's queried once and cached, if the cache if provided.
func getSingleLetterPerformers(ctx context.Context, c *Cache, reader PerformerAutoTagQueryer) ([]*models.Performer, error) {
func getSingleLetterPerformers(ctx context.Context, c *Cache, reader models.PerformerAutoTagQueryer) ([]*models.Performer, error) {
if c == nil {
c = &Cache{}
}
@ -53,7 +53,7 @@ func getSingleLetterPerformers(ctx context.Context, c *Cache, reader PerformerAu
// getSingleLetterStudios returns all studios with names that start with single character words.
// See getSingleLetterPerformers for details.
func getSingleLetterStudios(ctx context.Context, c *Cache, reader StudioAutoTagQueryer) ([]*models.Studio, error) {
func getSingleLetterStudios(ctx context.Context, c *Cache, reader models.StudioAutoTagQueryer) ([]*models.Studio, error) {
if c == nil {
c = &Cache{}
}
@ -86,7 +86,7 @@ func getSingleLetterStudios(ctx context.Context, c *Cache, reader StudioAutoTagQ
// getSingleLetterTags returns all tags with names that start with single character words.
// See getSingleLetterPerformers for details.
func getSingleLetterTags(ctx context.Context, c *Cache, reader TagAutoTagQueryer) ([]*models.Tag, error) {
func getSingleLetterTags(ctx context.Context, c *Cache, reader models.TagAutoTagQueryer) ([]*models.Tag, error) {
if c == nil {
c = &Cache{}
}

View file

@ -14,8 +14,6 @@ import (
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/scene"
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
"github.com/stashapp/stash/pkg/studio"
"github.com/stashapp/stash/pkg/tag"
)
const (
@ -28,24 +26,6 @@ const (
var separatorRE = regexp.MustCompile(separatorPattern)
type PerformerAutoTagQueryer interface {
Query(ctx context.Context, performerFilter *models.PerformerFilterType, findFilter *models.FindFilterType) ([]*models.Performer, int, error)
QueryForAutoTag(ctx context.Context, words []string) ([]*models.Performer, error)
models.AliasLoader
}
type StudioAutoTagQueryer interface {
QueryForAutoTag(ctx context.Context, words []string) ([]*models.Studio, error)
studio.Queryer
GetAliases(ctx context.Context, studioID int) ([]string, error)
}
type TagAutoTagQueryer interface {
QueryForAutoTag(ctx context.Context, words []string) ([]*models.Tag, error)
tag.Queryer
GetAliases(ctx context.Context, tagID int) ([]string, error)
}
func getPathQueryRegex(name string) string {
// escape specific regex characters
name = regexp.QuoteMeta(name)
@ -146,7 +126,7 @@ func regexpMatchesPath(r *regexp.Regexp, path string) int {
return found[len(found)-1][0]
}
func getPerformers(ctx context.Context, words []string, performerReader PerformerAutoTagQueryer, cache *Cache) ([]*models.Performer, error) {
func getPerformers(ctx context.Context, words []string, performerReader models.PerformerAutoTagQueryer, cache *Cache) ([]*models.Performer, error) {
performers, err := performerReader.QueryForAutoTag(ctx, words)
if err != nil {
return nil, err
@ -160,7 +140,7 @@ func getPerformers(ctx context.Context, words []string, performerReader Performe
return append(performers, swPerformers...), nil
}
func PathToPerformers(ctx context.Context, path string, reader PerformerAutoTagQueryer, cache *Cache, trimExt bool) ([]*models.Performer, error) {
func PathToPerformers(ctx context.Context, path string, reader models.PerformerAutoTagQueryer, cache *Cache, trimExt bool) ([]*models.Performer, error) {
words := getPathWords(path, trimExt)
performers, err := getPerformers(ctx, words, reader, cache)
@ -198,7 +178,7 @@ func PathToPerformers(ctx context.Context, path string, reader PerformerAutoTagQ
return ret, nil
}
func getStudios(ctx context.Context, words []string, reader StudioAutoTagQueryer, cache *Cache) ([]*models.Studio, error) {
func getStudios(ctx context.Context, words []string, reader models.StudioAutoTagQueryer, cache *Cache) ([]*models.Studio, error) {
studios, err := reader.QueryForAutoTag(ctx, words)
if err != nil {
return nil, err
@ -215,7 +195,7 @@ func getStudios(ctx context.Context, words []string, reader StudioAutoTagQueryer
// PathToStudio returns the Studio that matches the given path.
// Where multiple matching studios are found, the one that matches the latest
// position in the path is returned.
func PathToStudio(ctx context.Context, path string, reader StudioAutoTagQueryer, cache *Cache, trimExt bool) (*models.Studio, error) {
func PathToStudio(ctx context.Context, path string, reader models.StudioAutoTagQueryer, cache *Cache, trimExt bool) (*models.Studio, error) {
words := getPathWords(path, trimExt)
candidates, err := getStudios(ctx, words, reader, cache)
@ -249,7 +229,7 @@ func PathToStudio(ctx context.Context, path string, reader StudioAutoTagQueryer,
return ret, nil
}
func getTags(ctx context.Context, words []string, reader TagAutoTagQueryer, cache *Cache) ([]*models.Tag, error) {
func getTags(ctx context.Context, words []string, reader models.TagAutoTagQueryer, cache *Cache) ([]*models.Tag, error) {
tags, err := reader.QueryForAutoTag(ctx, words)
if err != nil {
return nil, err
@ -263,7 +243,7 @@ func getTags(ctx context.Context, words []string, reader TagAutoTagQueryer, cach
return append(tags, swTags...), nil
}
func PathToTags(ctx context.Context, path string, reader TagAutoTagQueryer, cache *Cache, trimExt bool) ([]*models.Tag, error) {
func PathToTags(ctx context.Context, path string, reader models.TagAutoTagQueryer, cache *Cache, trimExt bool) ([]*models.Tag, error) {
words := getPathWords(path, trimExt)
tags, err := getTags(ctx, words, reader, cache)
@ -299,7 +279,7 @@ func PathToTags(ctx context.Context, path string, reader TagAutoTagQueryer, cach
return ret, nil
}
func PathToScenesFn(ctx context.Context, name string, paths []string, sceneReader scene.Queryer, fn func(ctx context.Context, scene *models.Scene) error) error {
func PathToScenesFn(ctx context.Context, name string, paths []string, sceneReader models.SceneQueryer, fn func(ctx context.Context, scene *models.Scene) error) error {
regex := getPathQueryRegex(name)
organized := false
filter := models.SceneFilterType{
@ -358,7 +338,7 @@ func PathToScenesFn(ctx context.Context, name string, paths []string, sceneReade
return nil
}
func PathToImagesFn(ctx context.Context, name string, paths []string, imageReader image.Queryer, fn func(ctx context.Context, scene *models.Image) error) error {
func PathToImagesFn(ctx context.Context, name string, paths []string, imageReader models.ImageQueryer, fn func(ctx context.Context, scene *models.Image) error) error {
regex := getPathQueryRegex(name)
organized := false
filter := models.ImageFilterType{
@ -417,7 +397,7 @@ func PathToImagesFn(ctx context.Context, name string, paths []string, imageReade
return nil
}
func PathToGalleriesFn(ctx context.Context, name string, paths []string, galleryReader gallery.Queryer, fn func(ctx context.Context, scene *models.Gallery) error) error {
func PathToGalleriesFn(ctx context.Context, name string, paths []string, galleryReader models.GalleryQueryer, fn func(ctx context.Context, scene *models.Gallery) error) error {
regex := getPathQueryRegex(name)
organized := false
filter := models.GalleryFilterType{

View file

@ -58,7 +58,7 @@ func ScrapedPerformer(ctx context.Context, qb PerformerFinder, p *models.Scraped
}
type StudioFinder interface {
studio.Queryer
models.StudioQueryer
FindByStashID(ctx context.Context, stashID models.StashID) ([]*models.Studio, error)
}
@ -134,7 +134,7 @@ func ScrapedMovie(ctx context.Context, qb MovieNamesFinder, m *models.ScrapedMov
// ScrapedTag matches the provided tag with the tags
// in the database and sets the ID field if one is found.
func ScrapedTag(ctx context.Context, qb tag.Queryer, s *models.ScrapedTag) error {
func ScrapedTag(ctx context.Context, qb models.TagQueryer, s *models.ScrapedTag) error {
if s.StoredID != nil {
return nil
}

View file

@ -4,8 +4,6 @@ import (
"context"
"path/filepath"
"strings"
"github.com/stashapp/stash/pkg/file"
)
type FileQueryOptions struct {
@ -57,24 +55,24 @@ func PathsFileFilter(paths []string) *FileFilterType {
type FileQueryResult struct {
// can't use QueryResult because id type is wrong
IDs []file.ID
IDs []FileID
Count int
finder file.Finder
files []file.File
getter FileGetter
files []File
resolveErr error
}
func NewFileQueryResult(finder file.Finder) *FileQueryResult {
func NewFileQueryResult(fileGetter FileGetter) *FileQueryResult {
return &FileQueryResult{
finder: finder,
getter: fileGetter,
}
}
func (r *FileQueryResult) Resolve(ctx context.Context) ([]file.File, error) {
func (r *FileQueryResult) Resolve(ctx context.Context) ([]File, error) {
// cache results
if r.files == nil && r.resolveErr == nil {
r.files, r.resolveErr = r.finder.Find(ctx, r.IDs...)
r.files, r.resolveErr = r.getter.Find(ctx, r.IDs...)
}
return r.files, r.resolveErr
}

View file

@ -1,4 +1,9 @@
package file
package models
import (
"fmt"
"strconv"
)
var (
FingerprintTypeOshash = "oshash"
@ -12,6 +17,15 @@ type Fingerprint struct {
Fingerprint interface{}
}
func (f *Fingerprint) Value() string {
switch v := f.Fingerprint.(type) {
case int64:
return strconv.FormatUint(uint64(v), 16)
default:
return fmt.Sprintf("%v", f.Fingerprint)
}
}
type Fingerprints []Fingerprint
func (f *Fingerprints) Remove(type_ string) {
@ -114,8 +128,3 @@ func (f Fingerprints) AppendUnique(o Fingerprint) Fingerprints {
return append(f, o)
}
// FingerprintCalculator calculates a fingerprint for the provided file.
type FingerprintCalculator interface {
CalculateFingerprints(f *BaseFile, o Opener, useExisting bool) ([]Fingerprint, error)
}

View file

@ -1,4 +1,4 @@
package file
package models
import "testing"

Some files were not shown because too many files have changed in this diff Show more