Restructure data layer (#2532)

* Add new txn manager interface
* Add txn management to sqlite
* Rename get to getByID
* Add contexts to repository methods
* Update query builders
* Add context to reader writer interfaces
* Use repository in resolver
* Tighten interfaces
* Tighten interfaces in dlna
* Tighten interfaces in match package
* Tighten interfaces in scraper package
* Tighten interfaces in scan code
* Tighten interfaces on autotag package
* Remove ReaderWriter usage
* Merge database package into sqlite
This commit is contained in:
WithoutPants
2022-05-19 17:49:32 +10:00
parent 7b5bd80515
commit 964b559309
244 changed files with 7377 additions and 6699 deletions

View File

@@ -1,16 +1,23 @@
package movie
import (
"context"
"fmt"
"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"
"github.com/stashapp/stash/pkg/utils"
)
type ImageGetter interface {
GetFrontImage(ctx context.Context, movieID int) ([]byte, error)
GetBackImage(ctx context.Context, movieID int) ([]byte, error)
}
// ToJSON converts a Movie into its JSON equivalent.
func ToJSON(reader models.MovieReader, studioReader models.StudioReader, movie *models.Movie) (*jsonschema.Movie, error) {
func ToJSON(ctx context.Context, reader ImageGetter, studioReader studio.Finder, movie *models.Movie) (*jsonschema.Movie, error) {
newMovieJSON := jsonschema.Movie{
CreatedAt: json.JSONTime{Time: movie.CreatedAt.Timestamp},
UpdatedAt: json.JSONTime{Time: movie.UpdatedAt.Timestamp},
@@ -45,7 +52,7 @@ func ToJSON(reader models.MovieReader, studioReader models.StudioReader, movie *
}
if movie.StudioID.Valid {
studio, err := studioReader.Find(int(movie.StudioID.Int64))
studio, err := studioReader.Find(ctx, int(movie.StudioID.Int64))
if err != nil {
return nil, fmt.Errorf("error getting movie studio: %v", err)
}
@@ -55,7 +62,7 @@ func ToJSON(reader models.MovieReader, studioReader models.StudioReader, movie *
}
}
frontImage, err := reader.GetFrontImage(movie.ID)
frontImage, err := reader.GetFrontImage(ctx, movie.ID)
if err != nil {
return nil, fmt.Errorf("error getting movie front image: %v", err)
}
@@ -64,7 +71,7 @@ func ToJSON(reader models.MovieReader, studioReader models.StudioReader, movie *
newMovieJSON.FrontImage = utils.GetBase64StringFromData(frontImage)
}
backImage, err := reader.GetBackImage(movie.ID)
backImage, err := reader.GetBackImage(ctx, movie.ID)
if err != nil {
return nil, fmt.Errorf("error getting movie back image: %v", err)
}

View File

@@ -55,7 +55,7 @@ var (
backImageBytes = []byte("backImageBytes")
)
var studio models.Studio = models.Studio{
var movieStudio models.Studio = models.Studio{
Name: models.NullString(studioName),
}
@@ -189,30 +189,30 @@ func TestToJSON(t *testing.T) {
imageErr := errors.New("error getting image")
mockMovieReader.On("GetFrontImage", movieID).Return(frontImageBytes, nil).Once()
mockMovieReader.On("GetFrontImage", missingStudioMovieID).Return(frontImageBytes, nil).Once()
mockMovieReader.On("GetFrontImage", emptyID).Return(nil, nil).Once().Maybe()
mockMovieReader.On("GetFrontImage", errFrontImageID).Return(nil, imageErr).Once()
mockMovieReader.On("GetFrontImage", errBackImageID).Return(frontImageBytes, nil).Once()
mockMovieReader.On("GetFrontImage", testCtx, movieID).Return(frontImageBytes, nil).Once()
mockMovieReader.On("GetFrontImage", testCtx, missingStudioMovieID).Return(frontImageBytes, nil).Once()
mockMovieReader.On("GetFrontImage", testCtx, emptyID).Return(nil, nil).Once().Maybe()
mockMovieReader.On("GetFrontImage", testCtx, errFrontImageID).Return(nil, imageErr).Once()
mockMovieReader.On("GetFrontImage", testCtx, errBackImageID).Return(frontImageBytes, nil).Once()
mockMovieReader.On("GetBackImage", movieID).Return(backImageBytes, nil).Once()
mockMovieReader.On("GetBackImage", missingStudioMovieID).Return(backImageBytes, nil).Once()
mockMovieReader.On("GetBackImage", emptyID).Return(nil, nil).Once()
mockMovieReader.On("GetBackImage", errBackImageID).Return(nil, imageErr).Once()
mockMovieReader.On("GetBackImage", errFrontImageID).Return(backImageBytes, nil).Maybe()
mockMovieReader.On("GetBackImage", errStudioMovieID).Return(backImageBytes, nil).Maybe()
mockMovieReader.On("GetBackImage", testCtx, movieID).Return(backImageBytes, nil).Once()
mockMovieReader.On("GetBackImage", testCtx, missingStudioMovieID).Return(backImageBytes, nil).Once()
mockMovieReader.On("GetBackImage", testCtx, emptyID).Return(nil, nil).Once()
mockMovieReader.On("GetBackImage", testCtx, errBackImageID).Return(nil, imageErr).Once()
mockMovieReader.On("GetBackImage", testCtx, errFrontImageID).Return(backImageBytes, nil).Maybe()
mockMovieReader.On("GetBackImage", testCtx, errStudioMovieID).Return(backImageBytes, nil).Maybe()
mockStudioReader := &mocks.StudioReaderWriter{}
studioErr := errors.New("error getting studio")
mockStudioReader.On("Find", studioID).Return(&studio, nil)
mockStudioReader.On("Find", missingStudioID).Return(nil, nil)
mockStudioReader.On("Find", errStudioID).Return(nil, studioErr)
mockStudioReader.On("Find", testCtx, studioID).Return(&movieStudio, nil)
mockStudioReader.On("Find", testCtx, missingStudioID).Return(nil, nil)
mockStudioReader.On("Find", testCtx, errStudioID).Return(nil, studioErr)
for i, s := range scenarios {
movie := s.movie
json, err := ToJSON(mockMovieReader, mockStudioReader, &movie)
json, err := ToJSON(testCtx, mockMovieReader, mockStudioReader, &movie)
switch {
case !s.err && err != nil:

View File

@@ -1,18 +1,26 @@
package movie
import (
"context"
"database/sql"
"fmt"
"github.com/stashapp/stash/pkg/hash/md5"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/jsonschema"
"github.com/stashapp/stash/pkg/studio"
"github.com/stashapp/stash/pkg/utils"
)
type NameFinderCreatorUpdater interface {
NameFinderCreator
UpdateFull(ctx context.Context, updatedMovie models.Movie) (*models.Movie, error)
UpdateImages(ctx context.Context, movieID int, frontImage []byte, backImage []byte) error
}
type Importer struct {
ReaderWriter models.MovieReaderWriter
StudioWriter models.StudioReaderWriter
ReaderWriter NameFinderCreatorUpdater
StudioWriter studio.NameFinderCreator
Input jsonschema.Movie
MissingRefBehaviour models.ImportMissingRefEnum
@@ -21,10 +29,10 @@ type Importer struct {
backImageData []byte
}
func (i *Importer) PreImport() error {
func (i *Importer) PreImport(ctx context.Context) error {
i.movie = i.movieJSONToMovie(i.Input)
if err := i.populateStudio(); err != nil {
if err := i.populateStudio(ctx); err != nil {
return err
}
@@ -71,9 +79,9 @@ func (i *Importer) movieJSONToMovie(movieJSON jsonschema.Movie) models.Movie {
return newMovie
}
func (i *Importer) populateStudio() error {
func (i *Importer) populateStudio(ctx context.Context) error {
if i.Input.Studio != "" {
studio, err := i.StudioWriter.FindByName(i.Input.Studio, false)
studio, err := i.StudioWriter.FindByName(ctx, i.Input.Studio, false)
if err != nil {
return fmt.Errorf("error finding studio by name: %v", err)
}
@@ -88,7 +96,7 @@ func (i *Importer) populateStudio() error {
}
if i.MissingRefBehaviour == models.ImportMissingRefEnumCreate {
studioID, err := i.createStudio(i.Input.Studio)
studioID, err := i.createStudio(ctx, i.Input.Studio)
if err != nil {
return err
}
@@ -105,10 +113,10 @@ func (i *Importer) populateStudio() error {
return nil
}
func (i *Importer) createStudio(name string) (int, error) {
func (i *Importer) createStudio(ctx context.Context, name string) (int, error) {
newStudio := *models.NewStudio(name)
created, err := i.StudioWriter.Create(newStudio)
created, err := i.StudioWriter.Create(ctx, newStudio)
if err != nil {
return 0, err
}
@@ -116,9 +124,9 @@ func (i *Importer) createStudio(name string) (int, error) {
return created.ID, nil
}
func (i *Importer) PostImport(id int) error {
func (i *Importer) PostImport(ctx context.Context, id int) error {
if len(i.frontImageData) > 0 {
if err := i.ReaderWriter.UpdateImages(id, i.frontImageData, i.backImageData); err != nil {
if err := i.ReaderWriter.UpdateImages(ctx, id, i.frontImageData, i.backImageData); err != nil {
return fmt.Errorf("error setting movie images: %v", err)
}
}
@@ -130,9 +138,9 @@ func (i *Importer) Name() string {
return i.Input.Name
}
func (i *Importer) FindExistingID() (*int, error) {
func (i *Importer) FindExistingID(ctx context.Context) (*int, error) {
const nocase = false
existing, err := i.ReaderWriter.FindByName(i.Name(), nocase)
existing, err := i.ReaderWriter.FindByName(ctx, i.Name(), nocase)
if err != nil {
return nil, err
}
@@ -145,8 +153,8 @@ func (i *Importer) FindExistingID() (*int, error) {
return nil, nil
}
func (i *Importer) Create() (*int, error) {
created, err := i.ReaderWriter.Create(i.movie)
func (i *Importer) Create(ctx context.Context) (*int, error) {
created, err := i.ReaderWriter.Create(ctx, i.movie)
if err != nil {
return nil, fmt.Errorf("error creating movie: %v", err)
}
@@ -155,10 +163,10 @@ func (i *Importer) Create() (*int, error) {
return &id, nil
}
func (i *Importer) Update(id int) error {
func (i *Importer) Update(ctx context.Context, id int) error {
movie := i.movie
movie.ID = id
_, err := i.ReaderWriter.UpdateFull(movie)
_, err := i.ReaderWriter.UpdateFull(ctx, movie)
if err != nil {
return fmt.Errorf("error updating existing movie: %v", err)
}

View File

@@ -1,6 +1,7 @@
package movie
import (
"context"
"errors"
"testing"
@@ -27,6 +28,8 @@ const (
errImageID = 3
)
var testCtx = context.Background()
func TestImporterName(t *testing.T) {
i := Importer{
Input: jsonschema.Movie{
@@ -45,23 +48,23 @@ func TestImporterPreImport(t *testing.T) {
},
}
err := i.PreImport()
err := i.PreImport(testCtx)
assert.NotNil(t, err)
i.Input.FrontImage = frontImage
i.Input.BackImage = invalidImage
err = i.PreImport()
err = i.PreImport(testCtx)
assert.NotNil(t, err)
i.Input.BackImage = ""
err = i.PreImport()
err = i.PreImport(testCtx)
assert.Nil(t, err)
i.Input.BackImage = backImage
err = i.PreImport()
err = i.PreImport(testCtx)
assert.Nil(t, err)
}
@@ -79,17 +82,17 @@ func TestImporterPreImportWithStudio(t *testing.T) {
},
}
studioReaderWriter.On("FindByName", existingStudioName, false).Return(&models.Studio{
studioReaderWriter.On("FindByName", testCtx, existingStudioName, false).Return(&models.Studio{
ID: existingStudioID,
}, nil).Once()
studioReaderWriter.On("FindByName", existingStudioErr, false).Return(nil, errors.New("FindByName error")).Once()
studioReaderWriter.On("FindByName", testCtx, existingStudioErr, false).Return(nil, errors.New("FindByName error")).Once()
err := i.PreImport()
err := i.PreImport(testCtx)
assert.Nil(t, err)
assert.Equal(t, int64(existingStudioID), i.movie.StudioID.Int64)
i.Input.Studio = existingStudioErr
err = i.PreImport()
err = i.PreImport(testCtx)
assert.NotNil(t, err)
studioReaderWriter.AssertExpectations(t)
@@ -108,20 +111,20 @@ func TestImporterPreImportWithMissingStudio(t *testing.T) {
MissingRefBehaviour: models.ImportMissingRefEnumFail,
}
studioReaderWriter.On("FindByName", missingStudioName, false).Return(nil, nil).Times(3)
studioReaderWriter.On("Create", mock.AnythingOfType("models.Studio")).Return(&models.Studio{
studioReaderWriter.On("FindByName", testCtx, missingStudioName, false).Return(nil, nil).Times(3)
studioReaderWriter.On("Create", testCtx, mock.AnythingOfType("models.Studio")).Return(&models.Studio{
ID: existingStudioID,
}, nil)
err := i.PreImport()
err := i.PreImport(testCtx)
assert.NotNil(t, err)
i.MissingRefBehaviour = models.ImportMissingRefEnumIgnore
err = i.PreImport()
err = i.PreImport(testCtx)
assert.Nil(t, err)
i.MissingRefBehaviour = models.ImportMissingRefEnumCreate
err = i.PreImport()
err = i.PreImport(testCtx)
assert.Nil(t, err)
assert.Equal(t, int64(existingStudioID), i.movie.StudioID.Int64)
@@ -141,10 +144,10 @@ func TestImporterPreImportWithMissingStudioCreateErr(t *testing.T) {
MissingRefBehaviour: models.ImportMissingRefEnumCreate,
}
studioReaderWriter.On("FindByName", missingStudioName, false).Return(nil, nil).Once()
studioReaderWriter.On("Create", mock.AnythingOfType("models.Studio")).Return(nil, errors.New("Create error"))
studioReaderWriter.On("FindByName", testCtx, missingStudioName, false).Return(nil, nil).Once()
studioReaderWriter.On("Create", testCtx, mock.AnythingOfType("models.Studio")).Return(nil, errors.New("Create error"))
err := i.PreImport()
err := i.PreImport(testCtx)
assert.NotNil(t, err)
}
@@ -159,13 +162,13 @@ func TestImporterPostImport(t *testing.T) {
updateMovieImageErr := errors.New("UpdateImages error")
readerWriter.On("UpdateImages", movieID, frontImageBytes, backImageBytes).Return(nil).Once()
readerWriter.On("UpdateImages", errImageID, frontImageBytes, backImageBytes).Return(updateMovieImageErr).Once()
readerWriter.On("UpdateImages", testCtx, movieID, frontImageBytes, backImageBytes).Return(nil).Once()
readerWriter.On("UpdateImages", testCtx, errImageID, frontImageBytes, backImageBytes).Return(updateMovieImageErr).Once()
err := i.PostImport(movieID)
err := i.PostImport(testCtx, movieID)
assert.Nil(t, err)
err = i.PostImport(errImageID)
err = i.PostImport(testCtx, errImageID)
assert.NotNil(t, err)
readerWriter.AssertExpectations(t)
@@ -182,23 +185,23 @@ func TestImporterFindExistingID(t *testing.T) {
}
errFindByName := errors.New("FindByName error")
readerWriter.On("FindByName", movieName, false).Return(nil, nil).Once()
readerWriter.On("FindByName", existingMovieName, false).Return(&models.Movie{
readerWriter.On("FindByName", testCtx, movieName, false).Return(nil, nil).Once()
readerWriter.On("FindByName", testCtx, existingMovieName, false).Return(&models.Movie{
ID: existingMovieID,
}, nil).Once()
readerWriter.On("FindByName", movieNameErr, false).Return(nil, errFindByName).Once()
readerWriter.On("FindByName", testCtx, movieNameErr, false).Return(nil, errFindByName).Once()
id, err := i.FindExistingID()
id, err := i.FindExistingID(testCtx)
assert.Nil(t, id)
assert.Nil(t, err)
i.Input.Name = existingMovieName
id, err = i.FindExistingID()
id, err = i.FindExistingID(testCtx)
assert.Equal(t, existingMovieID, *id)
assert.Nil(t, err)
i.Input.Name = movieNameErr
id, err = i.FindExistingID()
id, err = i.FindExistingID(testCtx)
assert.Nil(t, id)
assert.NotNil(t, err)
@@ -222,17 +225,17 @@ func TestCreate(t *testing.T) {
}
errCreate := errors.New("Create error")
readerWriter.On("Create", movie).Return(&models.Movie{
readerWriter.On("Create", testCtx, movie).Return(&models.Movie{
ID: movieID,
}, nil).Once()
readerWriter.On("Create", movieErr).Return(nil, errCreate).Once()
readerWriter.On("Create", testCtx, movieErr).Return(nil, errCreate).Once()
id, err := i.Create()
id, err := i.Create(testCtx)
assert.Equal(t, movieID, *id)
assert.Nil(t, err)
i.movie = movieErr
id, err = i.Create()
id, err = i.Create(testCtx)
assert.Nil(t, id)
assert.NotNil(t, err)
@@ -259,18 +262,18 @@ func TestUpdate(t *testing.T) {
// id needs to be set for the mock input
movie.ID = movieID
readerWriter.On("UpdateFull", movie).Return(nil, nil).Once()
readerWriter.On("UpdateFull", testCtx, movie).Return(nil, nil).Once()
err := i.Update(movieID)
err := i.Update(testCtx, movieID)
assert.Nil(t, err)
i.movie = movieErr
// need to set id separately
movieErr.ID = errImageID
readerWriter.On("UpdateFull", movieErr).Return(nil, errUpdate).Once()
readerWriter.On("UpdateFull", testCtx, movieErr).Return(nil, errUpdate).Once()
err = i.Update(errImageID)
err = i.Update(testCtx, errImageID)
assert.NotNil(t, err)
readerWriter.AssertExpectations(t)

12
pkg/movie/update.go Normal file
View File

@@ -0,0 +1,12 @@
package movie
import (
"context"
"github.com/stashapp/stash/pkg/models"
)
type NameFinderCreator interface {
FindByName(ctx context.Context, name string, nocase bool) (*models.Movie, error)
Create(ctx context.Context, newMovie models.Movie) (*models.Movie, error)
}