mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 16:34:02 +01:00
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:
parent
20520a58b4
commit
c364346a59
185 changed files with 3840 additions and 2559 deletions
4
.mockery.yml
Normal file
4
.mockery.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
dir: ./pkg/models
|
||||
name: ".*ReaderWriter"
|
||||
outpkg: mocks
|
||||
output: ./pkg/models/mocks
|
||||
2
Makefile
2
Makefile
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
|||
30
gqlgen.yml
30
gqlgen.yml
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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{},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -377,9 +377,9 @@ func Test_sceneRelationships_tags(t *testing.T) {
|
|||
})).Return(errors.New("error creating tag"))
|
||||
|
||||
tr := sceneRelationships{
|
||||
sceneReader: mockSceneReaderWriter,
|
||||
tagCreatorFinder: mockTagReaderWriter,
|
||||
fieldOptions: make(map[string]*FieldOptions),
|
||||
sceneReader: mockSceneReaderWriter,
|
||||
tagCreator: mockTagReaderWriter,
|
||||
fieldOptions: make(map[string]*FieldOptions),
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
226
pkg/file/file.go
226
pkg/file/file.go
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
130
pkg/file/scan.go
130
pkg/file/scan.go
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
|
|
|
|||
|
|
@ -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{}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
Loading…
Reference in a new issue