Model refactor, part 3 (#4152)

* Remove manager.Repository
* Refactor other repositories
* Fix tests and add database mock
* Add AssertExpectations method
* Refactor routes
* Move default movie image to internal/static and add convenience methods
* Refactor default performer image boxes
This commit is contained in:
DingDongSoLong4
2023-10-16 05:26:34 +02:00
committed by GitHub
parent 40bcb4baa5
commit 33f2ebf2a3
87 changed files with 1843 additions and 1651 deletions

View File

@@ -1,12 +1,13 @@
package api
import (
"errors"
"fmt"
"io"
"io/fs"
"os"
"strings"
"github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/internal/static"
"github.com/stashapp/stash/pkg/hash"
"github.com/stashapp/stash/pkg/logger"
@@ -18,7 +19,7 @@ type imageBox struct {
files []string
}
var imageExtensions = []string{
var imageBoxExts = []string{
".jpg",
".jpeg",
".png",
@@ -42,7 +43,7 @@ func newImageBox(box fs.FS) (*imageBox, error) {
}
baseName := strings.ToLower(d.Name())
for _, ext := range imageExtensions {
for _, ext := range imageBoxExts {
if strings.HasSuffix(baseName, ext) {
ret.files = append(ret.files, path)
break
@@ -55,65 +56,14 @@ func newImageBox(box fs.FS) (*imageBox, error) {
return ret, err
}
var performerBox *imageBox
var performerBoxMale *imageBox
var performerBoxCustom *imageBox
func initialiseImages() {
var err error
performerBox, err = newImageBox(&static.Performer)
if err != nil {
logger.Warnf("error loading performer images: %v", err)
}
performerBoxMale, err = newImageBox(&static.PerformerMale)
if err != nil {
logger.Warnf("error loading male performer images: %v", err)
}
initialiseCustomImages()
}
func initialiseCustomImages() {
customPath := config.GetInstance().GetCustomPerformerImageLocation()
if customPath != "" {
logger.Debugf("Loading custom performer images from %s", customPath)
// We need to set performerBoxCustom at runtime, as this is a custom path, and store it in a pointer.
var err error
performerBoxCustom, err = newImageBox(os.DirFS(customPath))
if err != nil {
logger.Warnf("error loading custom performer from %s: %v", customPath, err)
}
} else {
performerBoxCustom = nil
}
}
func getRandomPerformerImageUsingName(name string, gender *models.GenderEnum, customPath string) ([]byte, error) {
var box *imageBox
// If we have a custom path, we should return a new box in the given path.
if performerBoxCustom != nil && len(performerBoxCustom.files) > 0 {
box = performerBoxCustom
func (box *imageBox) GetRandomImageByName(name string) ([]byte, error) {
files := box.files
if len(files) == 0 {
return nil, errors.New("box is empty")
}
var g models.GenderEnum
if gender != nil {
g = *gender
}
if box == nil {
switch g {
case models.GenderEnumFemale, models.GenderEnumTransgenderFemale:
box = performerBox
case models.GenderEnumMale, models.GenderEnumTransgenderMale:
box = performerBoxMale
default:
box = performerBox
}
}
imageFiles := box.files
index := hash.IntFromString(name) % uint64(len(imageFiles))
img, err := box.box.Open(imageFiles[index])
index := hash.IntFromString(name) % uint64(len(files))
img, err := box.box.Open(files[index])
if err != nil {
return nil, err
}
@@ -121,3 +71,64 @@ func getRandomPerformerImageUsingName(name string, gender *models.GenderEnum, cu
return io.ReadAll(img)
}
var performerBox *imageBox
var performerBoxMale *imageBox
var performerBoxCustom *imageBox
func init() {
var err error
performerBox, err = newImageBox(static.Sub(static.Performer))
if err != nil {
panic(fmt.Sprintf("loading performer images: %v", err))
}
performerBoxMale, err = newImageBox(static.Sub(static.PerformerMale))
if err != nil {
panic(fmt.Sprintf("loading male performer images: %v", err))
}
}
func initCustomPerformerImages(customPath string) {
if customPath != "" {
logger.Debugf("Loading custom performer images from %s", customPath)
var err error
performerBoxCustom, err = newImageBox(os.DirFS(customPath))
if err != nil {
logger.Warnf("error loading custom performer images from %s: %v", customPath, err)
}
} else {
performerBoxCustom = nil
}
}
func getDefaultPerformerImage(name string, gender *models.GenderEnum) []byte {
// try the custom box first if we have one
if performerBoxCustom != nil {
ret, err := performerBoxCustom.GetRandomImageByName(name)
if err == nil {
return ret
}
logger.Warnf("error loading custom default performer image: %v", err)
}
var g models.GenderEnum
if gender != nil {
g = *gender
}
var box *imageBox
switch g {
case models.GenderEnumFemale, models.GenderEnumTransgenderFemale:
box = performerBox
case models.GenderEnumMale, models.GenderEnumTransgenderMale:
box = performerBoxMale
default:
box = performerBox
}
ret, err := box.GetRandomImageByName(name)
if err != nil {
logger.Warnf("error loading default performer image: %v", err)
}
return ret
}

View File

@@ -17,9 +17,7 @@ import (
"net/http"
"time"
"github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/txn"
)
type contextKey struct{ name string }
@@ -49,8 +47,7 @@ type Loaders struct {
}
type Middleware struct {
DatabaseProvider txn.DatabaseProvider
Repository manager.Repository
Repository models.Repository
}
func (m Middleware) Middleware(next http.Handler) http.Handler {
@@ -131,13 +128,9 @@ func toErrorSlice(err error) []error {
return nil
}
func (m Middleware) withTxn(ctx context.Context, fn func(ctx context.Context) error) error {
return txn.WithDatabase(ctx, m.DatabaseProvider, fn)
}
func (m Middleware) fetchScenes(ctx context.Context) func(keys []int) ([]*models.Scene, []error) {
return func(keys []int) (ret []*models.Scene, errs []error) {
err := m.withTxn(ctx, func(ctx context.Context) error {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Scene.FindMany(ctx, keys)
return err
@@ -148,7 +141,7 @@ func (m Middleware) fetchScenes(ctx context.Context) func(keys []int) ([]*models
func (m Middleware) fetchImages(ctx context.Context) func(keys []int) ([]*models.Image, []error) {
return func(keys []int) (ret []*models.Image, errs []error) {
err := m.withTxn(ctx, func(ctx context.Context) error {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Image.FindMany(ctx, keys)
return err
@@ -160,7 +153,7 @@ func (m Middleware) fetchImages(ctx context.Context) func(keys []int) ([]*models
func (m Middleware) fetchGalleries(ctx context.Context) func(keys []int) ([]*models.Gallery, []error) {
return func(keys []int) (ret []*models.Gallery, errs []error) {
err := m.withTxn(ctx, func(ctx context.Context) error {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Gallery.FindMany(ctx, keys)
return err
@@ -172,7 +165,7 @@ func (m Middleware) fetchGalleries(ctx context.Context) func(keys []int) ([]*mod
func (m Middleware) fetchPerformers(ctx context.Context) func(keys []int) ([]*models.Performer, []error) {
return func(keys []int) (ret []*models.Performer, errs []error) {
err := m.withTxn(ctx, func(ctx context.Context) error {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Performer.FindMany(ctx, keys)
return err
@@ -184,7 +177,7 @@ func (m Middleware) fetchPerformers(ctx context.Context) func(keys []int) ([]*mo
func (m Middleware) fetchStudios(ctx context.Context) func(keys []int) ([]*models.Studio, []error) {
return func(keys []int) (ret []*models.Studio, errs []error) {
err := m.withTxn(ctx, func(ctx context.Context) error {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Studio.FindMany(ctx, keys)
return err
@@ -195,7 +188,7 @@ func (m Middleware) fetchStudios(ctx context.Context) func(keys []int) ([]*model
func (m Middleware) fetchTags(ctx context.Context) func(keys []int) ([]*models.Tag, []error) {
return func(keys []int) (ret []*models.Tag, errs []error) {
err := m.withTxn(ctx, func(ctx context.Context) error {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Tag.FindMany(ctx, keys)
return err
@@ -206,7 +199,7 @@ func (m Middleware) fetchTags(ctx context.Context) func(keys []int) ([]*models.T
func (m Middleware) fetchMovies(ctx context.Context) func(keys []int) ([]*models.Movie, []error) {
return func(keys []int) (ret []*models.Movie, errs []error) {
err := m.withTxn(ctx, func(ctx context.Context) error {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Movie.FindMany(ctx, keys)
return err
@@ -217,7 +210,7 @@ func (m Middleware) fetchMovies(ctx context.Context) func(keys []int) ([]*models
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 {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.File.Find(ctx, keys...)
return err
@@ -228,7 +221,7 @@ func (m Middleware) fetchFiles(ctx context.Context) func(keys []models.FileID) (
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 {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Scene.GetManyFileIDs(ctx, keys)
return err
@@ -239,7 +232,7 @@ func (m Middleware) fetchScenesFileIDs(ctx context.Context) func(keys []int) ([]
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 {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Image.GetManyFileIDs(ctx, keys)
return err
@@ -250,7 +243,7 @@ func (m Middleware) fetchImagesFileIDs(ctx context.Context) func(keys []int) ([]
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 {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Gallery.GetManyFileIDs(ctx, keys)
return err

View File

@@ -13,7 +13,7 @@ import (
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/plugin"
"github.com/stashapp/stash/pkg/scraper"
"github.com/stashapp/stash/pkg/txn"
"github.com/stashapp/stash/pkg/scraper/stashbox"
)
var (
@@ -33,8 +33,7 @@ type hookExecutor interface {
}
type Resolver struct {
txnManager txn.Manager
repository manager.Repository
repository models.Repository
sceneService manager.SceneService
imageService manager.ImageService
galleryService manager.GalleryService
@@ -102,11 +101,15 @@ type tagResolver struct{ *Resolver }
type savedFilterResolver struct{ *Resolver }
func (r *Resolver) withTxn(ctx context.Context, fn func(ctx context.Context) error) error {
return txn.WithTxn(ctx, r.txnManager, fn)
return r.repository.WithTxn(ctx, fn)
}
func (r *Resolver) withReadTxn(ctx context.Context, fn func(ctx context.Context) error) error {
return txn.WithReadTxn(ctx, r.txnManager, fn)
return r.repository.WithReadTxn(ctx, fn)
}
func (r *Resolver) stashboxRepository() stashbox.Repository {
return stashbox.NewRepository(r.repository)
}
func (r *queryResolver) MarkerWall(ctx context.Context, q *string) (ret []*models.SceneMarker, err error) {

View File

@@ -316,7 +316,7 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input ConfigGen
if input.CustomPerformerImageLocation != nil {
c.Set(config.CustomPerformerImageLocation, *input.CustomPerformerImageLocation)
initialiseCustomImages()
initCustomPerformerImages(*input.CustomPerformerImageLocation)
}
if input.ScraperUserAgent != nil {

View File

@@ -17,7 +17,7 @@ func (r *mutationResolver) MoveFiles(ctx context.Context, input MoveFilesInput)
fileStore := r.repository.File
folderStore := r.repository.Folder
mover := file.NewMover(fileStore, folderStore)
mover.RegisterHooks(ctx, r.txnManager)
mover.RegisterHooks(ctx)
var (
folder *models.Folder

View File

@@ -11,30 +11,30 @@ import (
)
func (r *mutationResolver) MigrateSceneScreenshots(ctx context.Context, input MigrateSceneScreenshotsInput) (string, error) {
db := manager.GetInstance().Database
mgr := manager.GetInstance()
t := &task.MigrateSceneScreenshotsJob{
ScreenshotsPath: manager.GetInstance().Paths.Generated.Screenshots,
Input: scene.MigrateSceneScreenshotsInput{
DeleteFiles: utils.IsTrue(input.DeleteFiles),
OverwriteExisting: utils.IsTrue(input.OverwriteExisting),
},
SceneRepo: db.Scene,
TxnManager: db,
SceneRepo: mgr.Repository.Scene,
TxnManager: mgr.Repository.TxnManager,
}
jobID := manager.GetInstance().JobManager.Add(ctx, "Migrating scene screenshots to blobs...", t)
jobID := mgr.JobManager.Add(ctx, "Migrating scene screenshots to blobs...", t)
return strconv.Itoa(jobID), nil
}
func (r *mutationResolver) MigrateBlobs(ctx context.Context, input MigrateBlobsInput) (string, error) {
db := manager.GetInstance().Database
mgr := manager.GetInstance()
t := &task.MigrateBlobsJob{
TxnManager: db,
BlobStore: db.Blobs,
Vacuumer: db,
TxnManager: mgr.Database,
BlobStore: mgr.Database.Blobs,
Vacuumer: mgr.Database,
DeleteOld: utils.IsTrue(input.DeleteOld),
}
jobID := manager.GetInstance().JobManager.Add(ctx, "Migrating blobs...", t)
jobID := mgr.JobManager.Add(ctx, "Migrating blobs...", t)
return strconv.Itoa(jobID), nil
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"strconv"
"github.com/stashapp/stash/internal/static"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/plugin"
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
@@ -50,12 +51,6 @@ func (r *mutationResolver) MovieCreate(ctx context.Context, input MovieCreateInp
return nil, fmt.Errorf("converting studio id: %w", err)
}
// HACK: if back image is being set, set the front image to the default.
// This is because we can't have a null front image with a non-null back image.
if input.FrontImage == nil && input.BackImage != nil {
input.FrontImage = &models.DefaultMovieImage
}
// Process the base 64 encoded image string
var frontimageData []byte
if input.FrontImage != nil {
@@ -74,6 +69,12 @@ func (r *mutationResolver) MovieCreate(ctx context.Context, input MovieCreateInp
}
}
// HACK: if back image is being set, set the front image to the default.
// This is because we can't have a null front image with a non-null back image.
if len(frontimageData) == 0 && len(backimageData) != 0 {
frontimageData = static.ReadAll(static.DefaultMovieImage)
}
// Start the transaction and save the movie
if err := r.withTxn(ctx, func(ctx context.Context) error {
qb := r.repository.Movie

View File

@@ -11,15 +11,6 @@ import (
"github.com/stashapp/stash/pkg/scraper/stashbox"
)
func (r *Resolver) stashboxRepository() stashbox.Repository {
return stashbox.Repository{
Scene: r.repository.Scene,
Performer: r.repository.Performer,
Tag: r.repository.Tag,
Studio: r.repository.Studio,
}
}
func (r *mutationResolver) SubmitStashBoxFingerprints(ctx context.Context, input StashBoxFingerprintSubmissionInput) (bool, error) {
boxes := config.GetInstance().GetStashBoxes()
@@ -27,7 +18,7 @@ func (r *mutationResolver) SubmitStashBoxFingerprints(ctx context.Context, input
return false, fmt.Errorf("invalid stash_box_index %d", input.StashBoxIndex)
}
client := stashbox.NewClient(*boxes[input.StashBoxIndex], r.txnManager, r.stashboxRepository())
client := stashbox.NewClient(*boxes[input.StashBoxIndex], r.stashboxRepository())
return client.SubmitStashBoxFingerprints(ctx, input.SceneIds, boxes[input.StashBoxIndex].Endpoint)
}
@@ -49,7 +40,7 @@ func (r *mutationResolver) SubmitStashBoxSceneDraft(ctx context.Context, input S
return nil, fmt.Errorf("invalid stash_box_index %d", input.StashBoxIndex)
}
client := stashbox.NewClient(*boxes[input.StashBoxIndex], r.txnManager, r.stashboxRepository())
client := stashbox.NewClient(*boxes[input.StashBoxIndex], r.stashboxRepository())
id, err := strconv.Atoi(input.ID)
if err != nil {
@@ -91,7 +82,7 @@ func (r *mutationResolver) SubmitStashBoxPerformerDraft(ctx context.Context, inp
return nil, fmt.Errorf("invalid stash_box_index %d", input.StashBoxIndex)
}
client := stashbox.NewClient(*boxes[input.StashBoxIndex], r.txnManager, r.stashboxRepository())
client := stashbox.NewClient(*boxes[input.StashBoxIndex], r.stashboxRepository())
id, err := strconv.Atoi(input.ID)
if err != nil {

View File

@@ -5,7 +5,6 @@ import (
"errors"
"testing"
"github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/mocks"
"github.com/stashapp/stash/pkg/plugin"
@@ -15,14 +14,9 @@ import (
)
// TODO - move this into a common area
func newResolver() *Resolver {
txnMgr := &mocks.TxnManager{}
func newResolver(db *mocks.Database) *Resolver {
return &Resolver{
txnManager: txnMgr,
repository: manager.Repository{
TxnManager: txnMgr,
Tag: &mocks.TagReaderWriter{},
},
repository: db.Repository(),
hookExecutor: &mockHookExecutor{},
}
}
@@ -45,9 +39,8 @@ func (*mockHookExecutor) ExecutePostHooks(ctx context.Context, id int, hookType
}
func TestTagCreate(t *testing.T) {
r := newResolver()
tagRW := r.repository.Tag.(*mocks.TagReaderWriter)
db := mocks.NewDatabase()
r := newResolver(db)
pp := 1
findFilter := &models.FindFilterType{
@@ -72,17 +65,17 @@ func TestTagCreate(t *testing.T) {
}
}
tagRW.On("Query", mock.Anything, tagFilterForName(existingTagName), findFilter).Return([]*models.Tag{
db.Tag.On("Query", mock.Anything, tagFilterForName(existingTagName), findFilter).Return([]*models.Tag{
{
ID: existingTagID,
Name: existingTagName,
},
}, 1, nil).Once()
tagRW.On("Query", mock.Anything, tagFilterForName(errTagName), findFilter).Return(nil, 0, nil).Once()
tagRW.On("Query", mock.Anything, tagFilterForAlias(errTagName), findFilter).Return(nil, 0, nil).Once()
db.Tag.On("Query", mock.Anything, tagFilterForName(errTagName), findFilter).Return(nil, 0, nil).Once()
db.Tag.On("Query", mock.Anything, tagFilterForAlias(errTagName), findFilter).Return(nil, 0, nil).Once()
expectedErr := errors.New("TagCreate error")
tagRW.On("Create", mock.Anything, mock.AnythingOfType("*models.Tag")).Return(expectedErr)
db.Tag.On("Create", mock.Anything, mock.AnythingOfType("*models.Tag")).Return(expectedErr)
// fails here because testCtx is empty
// TODO: Fix this
@@ -101,22 +94,22 @@ func TestTagCreate(t *testing.T) {
})
assert.Equal(t, expectedErr, err)
tagRW.AssertExpectations(t)
db.AssertExpectations(t)
r = newResolver()
tagRW = r.repository.Tag.(*mocks.TagReaderWriter)
db = mocks.NewDatabase()
r = newResolver(db)
tagRW.On("Query", mock.Anything, tagFilterForName(tagName), findFilter).Return(nil, 0, nil).Once()
tagRW.On("Query", mock.Anything, tagFilterForAlias(tagName), findFilter).Return(nil, 0, nil).Once()
db.Tag.On("Query", mock.Anything, tagFilterForName(tagName), findFilter).Return(nil, 0, nil).Once()
db.Tag.On("Query", mock.Anything, tagFilterForAlias(tagName), findFilter).Return(nil, 0, nil).Once()
newTag := &models.Tag{
ID: newTagID,
Name: tagName,
}
tagRW.On("Create", mock.Anything, mock.AnythingOfType("*models.Tag")).Run(func(args mock.Arguments) {
db.Tag.On("Create", mock.Anything, mock.AnythingOfType("*models.Tag")).Run(func(args mock.Arguments) {
arg := args.Get(1).(*models.Tag)
arg.ID = newTagID
}).Return(nil)
tagRW.On("Find", mock.Anything, newTagID).Return(newTag, nil)
db.Tag.On("Find", mock.Anything, newTagID).Return(newTag, nil)
tag, err := r.Mutation().TagCreate(testCtx, TagCreateInput{
Name: tagName,
@@ -124,4 +117,5 @@ func TestTagCreate(t *testing.T) {
assert.Nil(t, err)
assert.NotNil(t, tag)
db.AssertExpectations(t)
}

View File

@@ -243,7 +243,9 @@ func makeConfigUIResult() map[string]interface{} {
}
func (r *queryResolver) ValidateStashBoxCredentials(ctx context.Context, input config.StashBoxInput) (*StashBoxValidationResult, error) {
client := stashbox.NewClient(models.StashBox{Endpoint: input.Endpoint, APIKey: input.APIKey}, r.txnManager, r.stashboxRepository())
box := models.StashBox{Endpoint: input.Endpoint, APIKey: input.APIKey}
client := stashbox.NewClient(box, r.stashboxRepository())
user, err := client.GetUser(ctx)
valid := user != nil && user.Me != nil

View File

@@ -191,16 +191,11 @@ func (r *queryResolver) FindScenesByPathRegex(ctx context.Context, filter *model
}
func (r *queryResolver) ParseSceneFilenames(ctx context.Context, filter *models.FindFilterType, config models.SceneParserInput) (ret *SceneParserResultType, err error) {
parser := scene.NewFilenameParser(filter, config)
repo := scene.NewFilenameParserRepository(r.repository)
parser := scene.NewFilenameParser(filter, config, repo)
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
result, count, err := parser.Parse(ctx, scene.FilenameParserRepository{
Scene: r.repository.Scene,
Performer: r.repository.Performer,
Studio: r.repository.Studio,
Movie: r.repository.Movie,
Tag: r.repository.Tag,
})
result, count, err := parser.Parse(ctx)
if err != nil {
return err

View File

@@ -238,7 +238,7 @@ func (r *queryResolver) getStashBoxClient(index int) (*stashbox.Client, error) {
return nil, fmt.Errorf("%w: invalid stash_box_index %d", ErrInput, index)
}
return stashbox.NewClient(*boxes[index], r.txnManager, r.stashboxRepository()), nil
return stashbox.NewClient(*boxes[index], r.stashboxRepository()), nil
}
func (r *queryResolver) ScrapeSingleScene(ctx context.Context, source scraper.Source, input ScrapeSingleSceneInput) ([]*scraper.ScrapedScene, error) {

15
internal/api/routes.go Normal file
View File

@@ -0,0 +1,15 @@
package api
import (
"net/http"
"github.com/stashapp/stash/pkg/txn"
)
type routes struct {
txnManager txn.Manager
}
func (rs routes) withReadTxn(r *http.Request, fn txn.TxnFunc) error {
return txn.WithReadTxn(r.Context(), rs.txnManager, fn)
}

View File

@@ -12,6 +12,10 @@ type customRoutes struct {
servedFolders config.URLMap
}
func getCustomRoutes(servedFolders config.URLMap) chi.Router {
return customRoutes{servedFolders: servedFolders}.Routes()
}
func (rs customRoutes) Routes() chi.Router {
r := chi.NewRouter()

View File

@@ -10,6 +10,10 @@ import (
type downloadsRoutes struct{}
func getDownloadsRoutes() chi.Router {
return downloadsRoutes{}.Routes()
}
func (rs downloadsRoutes) Routes() chi.Router {
r := chi.NewRouter()

View File

@@ -3,7 +3,6 @@ package api
import (
"context"
"errors"
"io"
"io/fs"
"net/http"
"os/exec"
@@ -17,7 +16,6 @@ import (
"github.com/stashapp/stash/pkg/image"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/txn"
"github.com/stashapp/stash/pkg/utils"
)
@@ -27,11 +25,19 @@ type ImageFinder interface {
}
type imageRoutes struct {
txnManager txn.Manager
routes
imageFinder ImageFinder
fileGetter models.FileGetter
}
func getImageRoutes(repo models.Repository) chi.Router {
return imageRoutes{
routes: routes{txnManager: repo.TxnManager},
imageFinder: repo.Image,
fileGetter: repo.File,
}.Routes()
}
func (rs imageRoutes) Routes() chi.Router {
r := chi.NewRouter()
@@ -46,8 +52,6 @@ func (rs imageRoutes) Routes() chi.Router {
return r
}
// region Handlers
func (rs imageRoutes) Thumbnail(w http.ResponseWriter, r *http.Request) {
img := r.Context().Value(imageKey).(*models.Image)
filepath := manager.GetInstance().Paths.Generated.GetThumbnailPath(img.Checksum, models.DefaultGthumbWidth)
@@ -119,8 +123,6 @@ func (rs imageRoutes) Image(w http.ResponseWriter, r *http.Request) {
}
func (rs imageRoutes) serveImage(w http.ResponseWriter, r *http.Request, i *models.Image, useDefault bool) {
const defaultImageImage = "image/image.svg"
if i.Files.Primary() != nil {
err := i.Files.Primary().Base().Serve(&file.OsFS{}, w, r)
if err == nil {
@@ -141,22 +143,18 @@ func (rs imageRoutes) serveImage(w http.ResponseWriter, r *http.Request, i *mode
return
}
// fall back to static image
f, _ := static.Image.Open(defaultImageImage)
defer f.Close()
image, _ := io.ReadAll(f)
// fallback to default image
image := static.ReadAll(static.DefaultImageImage)
utils.ServeImage(w, r, image)
}
// endregion
func (rs imageRoutes) ImageCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
imageIdentifierQueryParam := chi.URLParam(r, "imageId")
imageID, _ := strconv.Atoi(imageIdentifierQueryParam)
var image *models.Image
_ = txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
_ = rs.withReadTxn(r, func(ctx context.Context) error {
qb := rs.imageFinder
if imageID == 0 {
images, _ := qb.FindByChecksum(ctx, imageIdentifierQueryParam)

View File

@@ -7,9 +7,10 @@ import (
"strconv"
"github.com/go-chi/chi"
"github.com/stashapp/stash/internal/static"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/txn"
"github.com/stashapp/stash/pkg/utils"
)
@@ -20,10 +21,17 @@ type MovieFinder interface {
}
type movieRoutes struct {
txnManager txn.Manager
routes
movieFinder MovieFinder
}
func getMovieRoutes(repo models.Repository) chi.Router {
return movieRoutes{
routes: routes{txnManager: repo.TxnManager},
movieFinder: repo.Movie,
}.Routes()
}
func (rs movieRoutes) Routes() chi.Router {
r := chi.NewRouter()
@@ -41,7 +49,7 @@ func (rs movieRoutes) FrontImage(w http.ResponseWriter, r *http.Request) {
defaultParam := r.URL.Query().Get("default")
var image []byte
if defaultParam != "true" {
readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
readTxnErr := rs.withReadTxn(r, func(ctx context.Context) error {
var err error
image, err = rs.movieFinder.GetFrontImage(ctx, movie.ID)
return err
@@ -54,8 +62,9 @@ func (rs movieRoutes) FrontImage(w http.ResponseWriter, r *http.Request) {
}
}
// fallback to default image
if len(image) == 0 {
image, _ = utils.ProcessBase64Image(models.DefaultMovieImage)
image = static.ReadAll(static.DefaultMovieImage)
}
utils.ServeImage(w, r, image)
@@ -66,7 +75,7 @@ func (rs movieRoutes) BackImage(w http.ResponseWriter, r *http.Request) {
defaultParam := r.URL.Query().Get("default")
var image []byte
if defaultParam != "true" {
readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
readTxnErr := rs.withReadTxn(r, func(ctx context.Context) error {
var err error
image, err = rs.movieFinder.GetBackImage(ctx, movie.ID)
return err
@@ -79,8 +88,9 @@ func (rs movieRoutes) BackImage(w http.ResponseWriter, r *http.Request) {
}
}
// fallback to default image
if len(image) == 0 {
image, _ = utils.ProcessBase64Image(models.DefaultMovieImage)
image = static.ReadAll(static.DefaultMovieImage)
}
utils.ServeImage(w, r, image)
@@ -95,7 +105,7 @@ func (rs movieRoutes) MovieCtx(next http.Handler) http.Handler {
}
var movie *models.Movie
_ = txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
_ = rs.withReadTxn(r, func(ctx context.Context) error {
movie, _ = rs.movieFinder.Find(ctx, movieID)
return nil
})

View File

@@ -7,10 +7,8 @@ import (
"strconv"
"github.com/go-chi/chi"
"github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/txn"
"github.com/stashapp/stash/pkg/utils"
)
@@ -20,10 +18,17 @@ type PerformerFinder interface {
}
type performerRoutes struct {
txnManager txn.Manager
routes
performerFinder PerformerFinder
}
func getPerformerRoutes(repo models.Repository) chi.Router {
return performerRoutes{
routes: routes{txnManager: repo.TxnManager},
performerFinder: repo.Performer,
}.Routes()
}
func (rs performerRoutes) Routes() chi.Router {
r := chi.NewRouter()
@@ -41,7 +46,7 @@ func (rs performerRoutes) Image(w http.ResponseWriter, r *http.Request) {
var image []byte
if defaultParam != "true" {
readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
readTxnErr := rs.withReadTxn(r, func(ctx context.Context) error {
var err error
image, err = rs.performerFinder.GetImage(ctx, performer.ID)
return err
@@ -55,7 +60,7 @@ func (rs performerRoutes) Image(w http.ResponseWriter, r *http.Request) {
}
if len(image) == 0 {
image, _ = getRandomPerformerImageUsingName(performer.Name, performer.Gender, config.GetInstance().GetCustomPerformerImageLocation())
image = getDefaultPerformerImage(performer.Name, performer.Gender)
}
utils.ServeImage(w, r, image)
@@ -70,7 +75,7 @@ func (rs performerRoutes) PerformerCtx(next http.Handler) http.Handler {
}
var performer *models.Performer
_ = txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
_ = rs.withReadTxn(r, func(ctx context.Context) error {
var err error
performer, err = rs.performerFinder.Find(ctx, performerID)
return err

View File

@@ -16,7 +16,6 @@ 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"
"github.com/stashapp/stash/pkg/utils"
)
@@ -43,7 +42,7 @@ type CaptionFinder interface {
}
type sceneRoutes struct {
txnManager txn.Manager
routes
sceneFinder SceneFinder
fileGetter models.FileGetter
captionFinder CaptionFinder
@@ -51,6 +50,17 @@ type sceneRoutes struct {
tagFinder SceneMarkerTagFinder
}
func getSceneRoutes(repo models.Repository) chi.Router {
return sceneRoutes{
routes: routes{txnManager: repo.TxnManager},
sceneFinder: repo.Scene,
fileGetter: repo.File,
captionFinder: repo.File,
sceneMarkerFinder: repo.SceneMarker,
tagFinder: repo.Tag,
}.Routes()
}
func (rs sceneRoutes) Routes() chi.Router {
r := chi.NewRouter()
@@ -89,8 +99,6 @@ func (rs sceneRoutes) Routes() chi.Router {
return r
}
// region Handlers
func (rs sceneRoutes) StreamDirect(w http.ResponseWriter, r *http.Request) {
scene := r.Context().Value(sceneKey).(*models.Scene)
ss := manager.SceneServer{
@@ -270,13 +278,13 @@ func (rs sceneRoutes) Webp(w http.ResponseWriter, r *http.Request) {
utils.ServeStaticFile(w, r, filepath)
}
func (rs sceneRoutes) getChapterVttTitle(ctx context.Context, marker *models.SceneMarker) (*string, error) {
func (rs sceneRoutes) getChapterVttTitle(r *http.Request, marker *models.SceneMarker) (*string, error) {
if marker.Title != "" {
return &marker.Title, nil
}
var title string
if err := txn.WithReadTxn(ctx, rs.txnManager, func(ctx context.Context) error {
if err := rs.withReadTxn(r, func(ctx context.Context) error {
qb := rs.tagFinder
primaryTag, err := qb.Find(ctx, marker.PrimaryTagID)
if err != nil {
@@ -305,7 +313,7 @@ func (rs sceneRoutes) getChapterVttTitle(ctx context.Context, marker *models.Sce
func (rs sceneRoutes) VttChapter(w http.ResponseWriter, r *http.Request) {
scene := r.Context().Value(sceneKey).(*models.Scene)
var sceneMarkers []*models.SceneMarker
readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
readTxnErr := rs.withReadTxn(r, func(ctx context.Context) error {
var err error
sceneMarkers, err = rs.sceneMarkerFinder.FindBySceneID(ctx, scene.ID)
return err
@@ -325,7 +333,7 @@ func (rs sceneRoutes) VttChapter(w http.ResponseWriter, r *http.Request) {
time := utils.GetVTTTime(marker.Seconds)
vttLines = append(vttLines, time+" --> "+time)
vttTitle, err := rs.getChapterVttTitle(r.Context(), marker)
vttTitle, err := rs.getChapterVttTitle(r, marker)
if errors.Is(err, context.Canceled) {
return
}
@@ -404,7 +412,7 @@ func (rs sceneRoutes) Caption(w http.ResponseWriter, r *http.Request, lang strin
s := r.Context().Value(sceneKey).(*models.Scene)
var captions []*models.VideoCaption
readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
readTxnErr := rs.withReadTxn(r, func(ctx context.Context) error {
var err error
primaryFile := s.Files.Primary()
if primaryFile == nil {
@@ -466,7 +474,7 @@ func (rs sceneRoutes) SceneMarkerStream(w http.ResponseWriter, r *http.Request)
sceneHash := scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm())
sceneMarkerID, _ := strconv.Atoi(chi.URLParam(r, "sceneMarkerId"))
var sceneMarker *models.SceneMarker
readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
readTxnErr := rs.withReadTxn(r, func(ctx context.Context) error {
var err error
sceneMarker, err = rs.sceneMarkerFinder.Find(ctx, sceneMarkerID)
return err
@@ -494,7 +502,7 @@ func (rs sceneRoutes) SceneMarkerPreview(w http.ResponseWriter, r *http.Request)
sceneHash := scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm())
sceneMarkerID, _ := strconv.Atoi(chi.URLParam(r, "sceneMarkerId"))
var sceneMarker *models.SceneMarker
readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
readTxnErr := rs.withReadTxn(r, func(ctx context.Context) error {
var err error
sceneMarker, err = rs.sceneMarkerFinder.Find(ctx, sceneMarkerID)
return err
@@ -530,7 +538,7 @@ func (rs sceneRoutes) SceneMarkerScreenshot(w http.ResponseWriter, r *http.Reque
sceneHash := scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm())
sceneMarkerID, _ := strconv.Atoi(chi.URLParam(r, "sceneMarkerId"))
var sceneMarker *models.SceneMarker
readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
readTxnErr := rs.withReadTxn(r, func(ctx context.Context) error {
var err error
sceneMarker, err = rs.sceneMarkerFinder.Find(ctx, sceneMarkerID)
return err
@@ -561,8 +569,6 @@ func (rs sceneRoutes) SceneMarkerScreenshot(w http.ResponseWriter, r *http.Reque
}
}
// endregion
func (rs sceneRoutes) SceneCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sceneID, err := strconv.Atoi(chi.URLParam(r, "sceneId"))
@@ -572,7 +578,7 @@ func (rs sceneRoutes) SceneCtx(next http.Handler) http.Handler {
}
var scene *models.Scene
_ = txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
_ = rs.withReadTxn(r, func(ctx context.Context) error {
qb := rs.sceneFinder
scene, _ = qb.Find(ctx, sceneID)

View File

@@ -3,7 +3,6 @@ package api
import (
"context"
"errors"
"io"
"net/http"
"strconv"
@@ -11,7 +10,6 @@ import (
"github.com/stashapp/stash/internal/static"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/txn"
"github.com/stashapp/stash/pkg/utils"
)
@@ -21,10 +19,17 @@ type StudioFinder interface {
}
type studioRoutes struct {
txnManager txn.Manager
routes
studioFinder StudioFinder
}
func getStudioRoutes(repo models.Repository) chi.Router {
return studioRoutes{
routes: routes{txnManager: repo.TxnManager},
studioFinder: repo.Studio,
}.Routes()
}
func (rs studioRoutes) Routes() chi.Router {
r := chi.NewRouter()
@@ -42,7 +47,7 @@ func (rs studioRoutes) Image(w http.ResponseWriter, r *http.Request) {
var image []byte
if defaultParam != "true" {
readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
readTxnErr := rs.withReadTxn(r, func(ctx context.Context) error {
var err error
image, err = rs.studioFinder.GetImage(ctx, studio.ID)
return err
@@ -55,15 +60,9 @@ func (rs studioRoutes) Image(w http.ResponseWriter, r *http.Request) {
}
}
// fallback to default image
if len(image) == 0 {
const defaultStudioImage = "studio/studio.svg"
// fall back to static image
f, _ := static.Studio.Open(defaultStudioImage)
defer f.Close()
stat, _ := f.Stat()
http.ServeContent(w, r, "studio.svg", stat.ModTime(), f.(io.ReadSeeker))
return
image = static.ReadAll(static.DefaultStudioImage)
}
utils.ServeImage(w, r, image)
@@ -78,7 +77,7 @@ func (rs studioRoutes) StudioCtx(next http.Handler) http.Handler {
}
var studio *models.Studio
_ = txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
_ = rs.withReadTxn(r, func(ctx context.Context) error {
var err error
studio, err = rs.studioFinder.Find(ctx, studioID)
return err

View File

@@ -3,7 +3,6 @@ package api
import (
"context"
"errors"
"io"
"net/http"
"strconv"
@@ -11,7 +10,6 @@ import (
"github.com/stashapp/stash/internal/static"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/txn"
"github.com/stashapp/stash/pkg/utils"
)
@@ -21,8 +19,15 @@ type TagFinder interface {
}
type tagRoutes struct {
txnManager txn.Manager
tagFinder TagFinder
routes
tagFinder TagFinder
}
func getTagRoutes(repo models.Repository) chi.Router {
return tagRoutes{
routes: routes{txnManager: repo.TxnManager},
tagFinder: repo.Tag,
}.Routes()
}
func (rs tagRoutes) Routes() chi.Router {
@@ -42,7 +47,7 @@ func (rs tagRoutes) Image(w http.ResponseWriter, r *http.Request) {
var image []byte
if defaultParam != "true" {
readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
readTxnErr := rs.withReadTxn(r, func(ctx context.Context) error {
var err error
image, err = rs.tagFinder.GetImage(ctx, tag.ID)
return err
@@ -55,15 +60,9 @@ func (rs tagRoutes) Image(w http.ResponseWriter, r *http.Request) {
}
}
// fallback to default image
if len(image) == 0 {
const defaultTagImage = "tag/tag.svg"
// fall back to static image
f, _ := static.Tag.Open(defaultTagImage)
defer f.Close()
stat, _ := f.Stat()
http.ServeContent(w, r, "tag.svg", stat.ModTime(), f.(io.ReadSeeker))
return
image = static.ReadAll(static.DefaultTagImage)
}
utils.ServeImage(w, r, image)
@@ -78,7 +77,7 @@ func (rs tagRoutes) TagCtx(next http.Handler) http.Handler {
}
var tag *models.Tag
_ = txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
_ = rs.withReadTxn(r, func(ctx context.Context) error {
var err error
tag, err = rs.tagFinder.Find(ctx, tagID)
return err

View File

@@ -50,7 +50,9 @@ var uiBox = ui.UIBox
var loginUIBox = ui.LoginUIBox
func Start() error {
initialiseImages()
c := config.GetInstance()
initCustomPerformerImages(c.GetCustomPerformerImageLocation())
r := chi.NewRouter()
@@ -62,7 +64,6 @@ func Start() error {
r.Use(middleware.Recoverer)
c := config.GetInstance()
if c.GetLogAccess() {
httpLogger := httplog.NewLogger("Stash", httplog.Options{
Concise: true,
@@ -82,11 +83,10 @@ func Start() error {
return errors.New(message)
}
txnManager := manager.GetInstance().Repository
repo := manager.GetInstance().Repository
dataloaders := loaders.Middleware{
DatabaseProvider: txnManager,
Repository: txnManager,
Repository: repo,
}
r.Use(dataloaders.Middleware)
@@ -96,8 +96,7 @@ func Start() error {
imageService := manager.GetInstance().ImageService
galleryService := manager.GetInstance().GalleryService
resolver := &Resolver{
txnManager: txnManager,
repository: txnManager,
repository: repo,
sceneService: sceneService,
imageService: imageService,
galleryService: galleryService,
@@ -144,36 +143,13 @@ func Start() error {
gqlPlayground.Handler("GraphQL playground", endpoint)(w, r)
})
r.Mount("/performer", performerRoutes{
txnManager: txnManager,
performerFinder: txnManager.Performer,
}.Routes())
r.Mount("/scene", sceneRoutes{
txnManager: txnManager,
sceneFinder: txnManager.Scene,
fileGetter: txnManager.File,
captionFinder: txnManager.File,
sceneMarkerFinder: txnManager.SceneMarker,
tagFinder: txnManager.Tag,
}.Routes())
r.Mount("/image", imageRoutes{
txnManager: txnManager,
imageFinder: txnManager.Image,
fileGetter: txnManager.File,
}.Routes())
r.Mount("/studio", studioRoutes{
txnManager: txnManager,
studioFinder: txnManager.Studio,
}.Routes())
r.Mount("/movie", movieRoutes{
txnManager: txnManager,
movieFinder: txnManager.Movie,
}.Routes())
r.Mount("/tag", tagRoutes{
txnManager: txnManager,
tagFinder: txnManager.Tag,
}.Routes())
r.Mount("/downloads", downloadsRoutes{}.Routes())
r.Mount("/performer", getPerformerRoutes(repo))
r.Mount("/scene", getSceneRoutes(repo))
r.Mount("/image", getImageRoutes(repo))
r.Mount("/studio", getStudioRoutes(repo))
r.Mount("/movie", getMovieRoutes(repo))
r.Mount("/tag", getTagRoutes(repo))
r.Mount("/downloads", getDownloadsRoutes())
r.HandleFunc("/css", cssHandler(c, pluginCache))
r.HandleFunc("/javascript", javascriptHandler(c, pluginCache))
@@ -193,9 +169,7 @@ func Start() error {
// Serve static folders
customServedFolders := c.GetCustomServedFolders()
if customServedFolders != nil {
r.Mount("/custom", customRoutes{
servedFolders: customServedFolders,
}.Routes())
r.Mount("/custom", getCustomRoutes(customServedFolders))
}
customUILocation := c.GetCustomUILocation()