mirror of https://github.com/stashapp/stash.git
Refactor autotag to use individual transactions (#3106)
* Add id filtering to scenes, images, and galleries * Perform tagging in batches * One transaction per object tagged
This commit is contained in:
parent
4a054ab081
commit
ce17230c13
|
@ -123,6 +123,7 @@ input SceneFilterType {
|
|||
OR: SceneFilterType
|
||||
NOT: SceneFilterType
|
||||
|
||||
id: IntCriterionInput
|
||||
title: StringCriterionInput
|
||||
code: StringCriterionInput
|
||||
details: StringCriterionInput
|
||||
|
@ -238,6 +239,7 @@ input GalleryFilterType {
|
|||
OR: GalleryFilterType
|
||||
NOT: GalleryFilterType
|
||||
|
||||
id: IntCriterionInput
|
||||
title: StringCriterionInput
|
||||
details: StringCriterionInput
|
||||
|
||||
|
@ -334,6 +336,8 @@ input ImageFilterType {
|
|||
|
||||
title: StringCriterionInput
|
||||
|
||||
""" Filter by image id"""
|
||||
id: IntCriterionInput
|
||||
"""Filter by file checksum"""
|
||||
checksum: StringCriterionInput
|
||||
"""Filter by path"""
|
||||
|
|
|
@ -479,6 +479,10 @@ func withTxn(f func(ctx context.Context) error) error {
|
|||
return txn.WithTxn(context.TODO(), db, f)
|
||||
}
|
||||
|
||||
func withDB(f func(ctx context.Context) error) error {
|
||||
return txn.WithDatabase(context.TODO(), db, f)
|
||||
}
|
||||
|
||||
func populateDB() error {
|
||||
if err := withTxn(func(ctx context.Context) error {
|
||||
err := createPerformer(ctx, r.Performer)
|
||||
|
@ -538,9 +542,13 @@ func TestParsePerformerScenes(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
tagger := Tagger{
|
||||
TxnManager: db,
|
||||
}
|
||||
|
||||
for _, p := range performers {
|
||||
if err := withTxn(func(ctx context.Context) error {
|
||||
return PerformerScenes(ctx, p, nil, r.Scene, nil)
|
||||
if err := withDB(func(ctx context.Context) error {
|
||||
return tagger.PerformerScenes(ctx, p, nil, r.Scene)
|
||||
}); err != nil {
|
||||
t.Errorf("Error auto-tagging performers: %s", err)
|
||||
}
|
||||
|
@ -585,14 +593,18 @@ func TestParseStudioScenes(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
tagger := Tagger{
|
||||
TxnManager: db,
|
||||
}
|
||||
|
||||
for _, s := range studios {
|
||||
if err := withTxn(func(ctx context.Context) error {
|
||||
if err := withDB(func(ctx context.Context) error {
|
||||
aliases, err := r.Studio.GetAliases(ctx, s.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return StudioScenes(ctx, s, nil, aliases, r.Scene, nil)
|
||||
return tagger.StudioScenes(ctx, s, nil, aliases, r.Scene)
|
||||
}); err != nil {
|
||||
t.Errorf("Error auto-tagging performers: %s", err)
|
||||
}
|
||||
|
@ -641,14 +653,18 @@ func TestParseTagScenes(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
tagger := Tagger{
|
||||
TxnManager: db,
|
||||
}
|
||||
|
||||
for _, s := range tags {
|
||||
if err := withTxn(func(ctx context.Context) error {
|
||||
if err := withDB(func(ctx context.Context) error {
|
||||
aliases, err := r.Tag.GetAliases(ctx, s.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return TagScenes(ctx, s, nil, aliases, r.Scene, nil)
|
||||
return tagger.TagScenes(ctx, s, nil, aliases, r.Scene)
|
||||
}); err != nil {
|
||||
t.Errorf("Error auto-tagging performers: %s", err)
|
||||
}
|
||||
|
@ -693,9 +709,13 @@ func TestParsePerformerImages(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
tagger := Tagger{
|
||||
TxnManager: db,
|
||||
}
|
||||
|
||||
for _, p := range performers {
|
||||
if err := withTxn(func(ctx context.Context) error {
|
||||
return PerformerImages(ctx, p, nil, r.Image, nil)
|
||||
if err := withDB(func(ctx context.Context) error {
|
||||
return tagger.PerformerImages(ctx, p, nil, r.Image)
|
||||
}); err != nil {
|
||||
t.Errorf("Error auto-tagging performers: %s", err)
|
||||
}
|
||||
|
@ -741,14 +761,18 @@ func TestParseStudioImages(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
tagger := Tagger{
|
||||
TxnManager: db,
|
||||
}
|
||||
|
||||
for _, s := range studios {
|
||||
if err := withTxn(func(ctx context.Context) error {
|
||||
if err := withDB(func(ctx context.Context) error {
|
||||
aliases, err := r.Studio.GetAliases(ctx, s.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return StudioImages(ctx, s, nil, aliases, r.Image, nil)
|
||||
return tagger.StudioImages(ctx, s, nil, aliases, r.Image)
|
||||
}); err != nil {
|
||||
t.Errorf("Error auto-tagging performers: %s", err)
|
||||
}
|
||||
|
@ -797,14 +821,18 @@ func TestParseTagImages(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
tagger := Tagger{
|
||||
TxnManager: db,
|
||||
}
|
||||
|
||||
for _, s := range tags {
|
||||
if err := withTxn(func(ctx context.Context) error {
|
||||
if err := withDB(func(ctx context.Context) error {
|
||||
aliases, err := r.Tag.GetAliases(ctx, s.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return TagImages(ctx, s, nil, aliases, r.Image, nil)
|
||||
return tagger.TagImages(ctx, s, nil, aliases, r.Image)
|
||||
}); err != nil {
|
||||
t.Errorf("Error auto-tagging performers: %s", err)
|
||||
}
|
||||
|
@ -850,9 +878,13 @@ func TestParsePerformerGalleries(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
tagger := Tagger{
|
||||
TxnManager: db,
|
||||
}
|
||||
|
||||
for _, p := range performers {
|
||||
if err := withTxn(func(ctx context.Context) error {
|
||||
return PerformerGalleries(ctx, p, nil, r.Gallery, nil)
|
||||
if err := withDB(func(ctx context.Context) error {
|
||||
return tagger.PerformerGalleries(ctx, p, nil, r.Gallery)
|
||||
}); err != nil {
|
||||
t.Errorf("Error auto-tagging performers: %s", err)
|
||||
}
|
||||
|
@ -898,14 +930,18 @@ func TestParseStudioGalleries(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
tagger := Tagger{
|
||||
TxnManager: db,
|
||||
}
|
||||
|
||||
for _, s := range studios {
|
||||
if err := withTxn(func(ctx context.Context) error {
|
||||
if err := withDB(func(ctx context.Context) error {
|
||||
aliases, err := r.Studio.GetAliases(ctx, s.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return StudioGalleries(ctx, s, nil, aliases, r.Gallery, nil)
|
||||
return tagger.StudioGalleries(ctx, s, nil, aliases, r.Gallery)
|
||||
}); err != nil {
|
||||
t.Errorf("Error auto-tagging performers: %s", err)
|
||||
}
|
||||
|
@ -954,14 +990,18 @@ func TestParseTagGalleries(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
tagger := Tagger{
|
||||
TxnManager: db,
|
||||
}
|
||||
|
||||
for _, s := range tags {
|
||||
if err := withTxn(func(ctx context.Context) error {
|
||||
if err := withDB(func(ctx context.Context) error {
|
||||
aliases, err := r.Tag.GetAliases(ctx, s.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return TagGalleries(ctx, s, nil, aliases, r.Gallery, nil)
|
||||
return tagger.TagGalleries(ctx, s, nil, aliases, r.Gallery)
|
||||
}); err != nil {
|
||||
t.Errorf("Error auto-tagging performers: %s", err)
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/scene"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/intslice"
|
||||
"github.com/stashapp/stash/pkg/txn"
|
||||
)
|
||||
|
||||
type SceneQueryPerformerUpdater interface {
|
||||
|
@ -39,8 +40,8 @@ func getPerformerTagger(p *models.Performer, cache *match.Cache) tagger {
|
|||
}
|
||||
|
||||
// PerformerScenes searches for scenes whose path matches the provided performer name and tags the scene with the performer.
|
||||
func PerformerScenes(ctx context.Context, p *models.Performer, paths []string, rw SceneQueryPerformerUpdater, cache *match.Cache) error {
|
||||
t := getPerformerTagger(p, cache)
|
||||
func (tagger *Tagger) PerformerScenes(ctx context.Context, p *models.Performer, paths []string, rw SceneQueryPerformerUpdater) error {
|
||||
t := getPerformerTagger(p, tagger.Cache)
|
||||
|
||||
return t.tagScenes(ctx, paths, rw, func(o *models.Scene) (bool, error) {
|
||||
if err := o.LoadPerformerIDs(ctx, rw); err != nil {
|
||||
|
@ -52,7 +53,9 @@ func PerformerScenes(ctx context.Context, p *models.Performer, paths []string, r
|
|||
return false, nil
|
||||
}
|
||||
|
||||
if err := scene.AddPerformer(ctx, rw, o, p.ID); err != nil {
|
||||
if err := txn.WithTxn(ctx, tagger.TxnManager, func(ctx context.Context) error {
|
||||
return scene.AddPerformer(ctx, rw, o, p.ID)
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
|
@ -61,8 +64,8 @@ func PerformerScenes(ctx context.Context, p *models.Performer, paths []string, r
|
|||
}
|
||||
|
||||
// PerformerImages searches for images whose path matches the provided performer name and tags the image with the performer.
|
||||
func PerformerImages(ctx context.Context, p *models.Performer, paths []string, rw ImageQueryPerformerUpdater, cache *match.Cache) error {
|
||||
t := getPerformerTagger(p, cache)
|
||||
func (tagger *Tagger) PerformerImages(ctx context.Context, p *models.Performer, paths []string, rw ImageQueryPerformerUpdater) error {
|
||||
t := getPerformerTagger(p, tagger.Cache)
|
||||
|
||||
return t.tagImages(ctx, paths, rw, func(o *models.Image) (bool, error) {
|
||||
if err := o.LoadPerformerIDs(ctx, rw); err != nil {
|
||||
|
@ -74,7 +77,9 @@ func PerformerImages(ctx context.Context, p *models.Performer, paths []string, r
|
|||
return false, nil
|
||||
}
|
||||
|
||||
if err := image.AddPerformer(ctx, rw, o, p.ID); err != nil {
|
||||
if err := txn.WithTxn(ctx, tagger.TxnManager, func(ctx context.Context) error {
|
||||
return image.AddPerformer(ctx, rw, o, p.ID)
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
|
@ -83,8 +88,8 @@ func PerformerImages(ctx context.Context, p *models.Performer, paths []string, r
|
|||
}
|
||||
|
||||
// PerformerGalleries searches for galleries whose path matches the provided performer name and tags the gallery with the performer.
|
||||
func PerformerGalleries(ctx context.Context, p *models.Performer, paths []string, rw GalleryQueryPerformerUpdater, cache *match.Cache) error {
|
||||
t := getPerformerTagger(p, cache)
|
||||
func (tagger *Tagger) PerformerGalleries(ctx context.Context, p *models.Performer, paths []string, rw GalleryQueryPerformerUpdater) error {
|
||||
t := getPerformerTagger(p, tagger.Cache)
|
||||
|
||||
return t.tagGalleries(ctx, paths, rw, func(o *models.Gallery) (bool, error) {
|
||||
if err := o.LoadPerformerIDs(ctx, rw); err != nil {
|
||||
|
@ -96,7 +101,9 @@ func PerformerGalleries(ctx context.Context, p *models.Performer, paths []string
|
|||
return false, nil
|
||||
}
|
||||
|
||||
if err := gallery.AddPerformer(ctx, rw, o, p.ID); err != nil {
|
||||
if err := txn.WithTxn(ctx, tagger.TxnManager, func(ctx context.Context) error {
|
||||
return gallery.AddPerformer(ctx, rw, o, p.ID)
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/stashapp/stash/pkg/models/mocks"
|
||||
"github.com/stashapp/stash/pkg/scene"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
func TestPerformerScenes(t *testing.T) {
|
||||
|
@ -64,7 +65,9 @@ func testPerformerScenes(t *testing.T, performerName, expectedRegex string) {
|
|||
}
|
||||
|
||||
organized := false
|
||||
perPage := models.PerPageAll
|
||||
perPage := 1000
|
||||
sort := "id"
|
||||
direction := models.SortDirectionEnumAsc
|
||||
|
||||
expectedSceneFilter := &models.SceneFilterType{
|
||||
Organized: &organized,
|
||||
|
@ -75,15 +78,17 @@ func testPerformerScenes(t *testing.T, performerName, expectedRegex string) {
|
|||
}
|
||||
|
||||
expectedFindFilter := &models.FindFilterType{
|
||||
PerPage: &perPage,
|
||||
PerPage: &perPage,
|
||||
Sort: &sort,
|
||||
Direction: &direction,
|
||||
}
|
||||
|
||||
mockSceneReader.On("Query", testCtx, scene.QueryOptions(expectedSceneFilter, expectedFindFilter, false)).
|
||||
mockSceneReader.On("Query", mock.Anything, scene.QueryOptions(expectedSceneFilter, expectedFindFilter, false)).
|
||||
Return(mocks.SceneQueryResult(scenes, len(scenes)), nil).Once()
|
||||
|
||||
for i := range matchingPaths {
|
||||
sceneID := i + 1
|
||||
mockSceneReader.On("UpdatePartial", testCtx, sceneID, models.ScenePartial{
|
||||
mockSceneReader.On("UpdatePartial", mock.Anything, sceneID, models.ScenePartial{
|
||||
PerformerIDs: &models.UpdateIDs{
|
||||
IDs: []int{performerID},
|
||||
Mode: models.RelationshipUpdateModeAdd,
|
||||
|
@ -91,7 +96,11 @@ func testPerformerScenes(t *testing.T, performerName, expectedRegex string) {
|
|||
}).Return(nil, nil).Once()
|
||||
}
|
||||
|
||||
err := PerformerScenes(testCtx, &performer, nil, mockSceneReader, nil)
|
||||
tagger := Tagger{
|
||||
TxnManager: &mocks.TxnManager{},
|
||||
}
|
||||
|
||||
err := tagger.PerformerScenes(testCtx, &performer, nil, mockSceneReader)
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
|
@ -144,7 +153,9 @@ func testPerformerImages(t *testing.T, performerName, expectedRegex string) {
|
|||
}
|
||||
|
||||
organized := false
|
||||
perPage := models.PerPageAll
|
||||
perPage := 1000
|
||||
sort := "id"
|
||||
direction := models.SortDirectionEnumAsc
|
||||
|
||||
expectedImageFilter := &models.ImageFilterType{
|
||||
Organized: &organized,
|
||||
|
@ -155,15 +166,17 @@ func testPerformerImages(t *testing.T, performerName, expectedRegex string) {
|
|||
}
|
||||
|
||||
expectedFindFilter := &models.FindFilterType{
|
||||
PerPage: &perPage,
|
||||
PerPage: &perPage,
|
||||
Sort: &sort,
|
||||
Direction: &direction,
|
||||
}
|
||||
|
||||
mockImageReader.On("Query", testCtx, image.QueryOptions(expectedImageFilter, expectedFindFilter, false)).
|
||||
mockImageReader.On("Query", mock.Anything, image.QueryOptions(expectedImageFilter, expectedFindFilter, false)).
|
||||
Return(mocks.ImageQueryResult(images, len(images)), nil).Once()
|
||||
|
||||
for i := range matchingPaths {
|
||||
imageID := i + 1
|
||||
mockImageReader.On("UpdatePartial", testCtx, imageID, models.ImagePartial{
|
||||
mockImageReader.On("UpdatePartial", mock.Anything, imageID, models.ImagePartial{
|
||||
PerformerIDs: &models.UpdateIDs{
|
||||
IDs: []int{performerID},
|
||||
Mode: models.RelationshipUpdateModeAdd,
|
||||
|
@ -171,7 +184,11 @@ func testPerformerImages(t *testing.T, performerName, expectedRegex string) {
|
|||
}).Return(nil, nil).Once()
|
||||
}
|
||||
|
||||
err := PerformerImages(testCtx, &performer, nil, mockImageReader, nil)
|
||||
tagger := Tagger{
|
||||
TxnManager: &mocks.TxnManager{},
|
||||
}
|
||||
|
||||
err := tagger.PerformerImages(testCtx, &performer, nil, mockImageReader)
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
|
@ -225,7 +242,9 @@ func testPerformerGalleries(t *testing.T, performerName, expectedRegex string) {
|
|||
}
|
||||
|
||||
organized := false
|
||||
perPage := models.PerPageAll
|
||||
perPage := 1000
|
||||
sort := "id"
|
||||
direction := models.SortDirectionEnumAsc
|
||||
|
||||
expectedGalleryFilter := &models.GalleryFilterType{
|
||||
Organized: &organized,
|
||||
|
@ -236,14 +255,16 @@ func testPerformerGalleries(t *testing.T, performerName, expectedRegex string) {
|
|||
}
|
||||
|
||||
expectedFindFilter := &models.FindFilterType{
|
||||
PerPage: &perPage,
|
||||
PerPage: &perPage,
|
||||
Sort: &sort,
|
||||
Direction: &direction,
|
||||
}
|
||||
|
||||
mockGalleryReader.On("Query", testCtx, expectedGalleryFilter, expectedFindFilter).Return(galleries, len(galleries), nil).Once()
|
||||
mockGalleryReader.On("Query", mock.Anything, expectedGalleryFilter, expectedFindFilter).Return(galleries, len(galleries), nil).Once()
|
||||
|
||||
for i := range matchingPaths {
|
||||
galleryID := i + 1
|
||||
mockGalleryReader.On("UpdatePartial", testCtx, galleryID, models.GalleryPartial{
|
||||
mockGalleryReader.On("UpdatePartial", mock.Anything, galleryID, models.GalleryPartial{
|
||||
PerformerIDs: &models.UpdateIDs{
|
||||
IDs: []int{performerID},
|
||||
Mode: models.RelationshipUpdateModeAdd,
|
||||
|
@ -251,7 +272,11 @@ func testPerformerGalleries(t *testing.T, performerName, expectedRegex string) {
|
|||
}).Return(nil, nil).Once()
|
||||
}
|
||||
|
||||
err := PerformerGalleries(testCtx, &performer, nil, mockGalleryReader, nil)
|
||||
tagger := Tagger{
|
||||
TxnManager: &mocks.TxnManager{},
|
||||
}
|
||||
|
||||
err := tagger.PerformerGalleries(testCtx, &performer, nil, mockGalleryReader)
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
|
|
|
@ -8,8 +8,12 @@ import (
|
|||
"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) {
|
||||
// don't set if already set
|
||||
if o.StudioID != nil {
|
||||
|
@ -86,12 +90,28 @@ type SceneFinderUpdater interface {
|
|||
}
|
||||
|
||||
// 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 StudioScenes(ctx context.Context, p *models.Studio, paths []string, aliases []string, rw SceneFinderUpdater, cache *match.Cache) error {
|
||||
t := getStudioTagger(p, aliases, cache)
|
||||
func (tagger *Tagger) StudioScenes(ctx context.Context, p *models.Studio, paths []string, aliases []string, rw SceneFinderUpdater) error {
|
||||
t := getStudioTagger(p, aliases, tagger.Cache)
|
||||
|
||||
for _, tt := range t {
|
||||
if err := tt.tagScenes(ctx, paths, rw, func(o *models.Scene) (bool, error) {
|
||||
return addSceneStudio(ctx, rw, o, p.ID)
|
||||
// don't set if already set
|
||||
if o.StudioID != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// set the studio id
|
||||
scenePartial := models.ScenePartial{
|
||||
StudioID: models.NewOptionalInt(p.ID),
|
||||
}
|
||||
|
||||
if err := txn.WithTxn(ctx, tagger.TxnManager, func(ctx context.Context) error {
|
||||
_, err := rw.UpdatePartial(ctx, o.ID, scenePartial)
|
||||
return err
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -107,12 +127,28 @@ type ImageFinderUpdater interface {
|
|||
}
|
||||
|
||||
// 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 StudioImages(ctx context.Context, p *models.Studio, paths []string, aliases []string, rw ImageFinderUpdater, cache *match.Cache) error {
|
||||
t := getStudioTagger(p, aliases, cache)
|
||||
func (tagger *Tagger) StudioImages(ctx context.Context, p *models.Studio, paths []string, aliases []string, rw ImageFinderUpdater) error {
|
||||
t := getStudioTagger(p, aliases, tagger.Cache)
|
||||
|
||||
for _, tt := range t {
|
||||
if err := tt.tagImages(ctx, paths, rw, func(i *models.Image) (bool, error) {
|
||||
return addImageStudio(ctx, rw, i, p.ID)
|
||||
// don't set if already set
|
||||
if i.StudioID != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// set the studio id
|
||||
imagePartial := models.ImagePartial{
|
||||
StudioID: models.NewOptionalInt(p.ID),
|
||||
}
|
||||
|
||||
if err := txn.WithTxn(ctx, tagger.TxnManager, func(ctx context.Context) error {
|
||||
_, err := rw.UpdatePartial(ctx, i.ID, imagePartial)
|
||||
return err
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -128,12 +164,28 @@ type GalleryFinderUpdater interface {
|
|||
}
|
||||
|
||||
// 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 StudioGalleries(ctx context.Context, p *models.Studio, paths []string, aliases []string, rw GalleryFinderUpdater, cache *match.Cache) error {
|
||||
t := getStudioTagger(p, aliases, cache)
|
||||
func (tagger *Tagger) StudioGalleries(ctx context.Context, p *models.Studio, paths []string, aliases []string, rw GalleryFinderUpdater) error {
|
||||
t := getStudioTagger(p, aliases, tagger.Cache)
|
||||
|
||||
for _, tt := range t {
|
||||
if err := tt.tagGalleries(ctx, paths, rw, func(o *models.Gallery) (bool, error) {
|
||||
return addGalleryStudio(ctx, rw, o, p.ID)
|
||||
// don't set if already set
|
||||
if o.StudioID != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// set the studio id
|
||||
galleryPartial := models.GalleryPartial{
|
||||
StudioID: models.NewOptionalInt(p.ID),
|
||||
}
|
||||
|
||||
if err := txn.WithTxn(ctx, tagger.TxnManager, func(ctx context.Context) error {
|
||||
_, err := rw.UpdatePartial(ctx, o.ID, galleryPartial)
|
||||
return err
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/stashapp/stash/pkg/models/mocks"
|
||||
"github.com/stashapp/stash/pkg/scene"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type testStudioCase struct {
|
||||
|
@ -110,7 +111,9 @@ func testStudioScenes(t *testing.T, tc testStudioCase) {
|
|||
}
|
||||
|
||||
organized := false
|
||||
perPage := models.PerPageAll
|
||||
perPage := 1000
|
||||
sort := "id"
|
||||
direction := models.SortDirectionEnumAsc
|
||||
|
||||
expectedSceneFilter := &models.SceneFilterType{
|
||||
Organized: &organized,
|
||||
|
@ -121,7 +124,9 @@ func testStudioScenes(t *testing.T, tc testStudioCase) {
|
|||
}
|
||||
|
||||
expectedFindFilter := &models.FindFilterType{
|
||||
PerPage: &perPage,
|
||||
PerPage: &perPage,
|
||||
Sort: &sort,
|
||||
Direction: &direction,
|
||||
}
|
||||
|
||||
// if alias provided, then don't find by name
|
||||
|
@ -140,19 +145,23 @@ func testStudioScenes(t *testing.T, tc testStudioCase) {
|
|||
},
|
||||
}
|
||||
|
||||
mockSceneReader.On("Query", testCtx, scene.QueryOptions(expectedAliasFilter, expectedFindFilter, false)).
|
||||
mockSceneReader.On("Query", mock.Anything, scene.QueryOptions(expectedAliasFilter, expectedFindFilter, false)).
|
||||
Return(mocks.SceneQueryResult(scenes, len(scenes)), nil).Once()
|
||||
}
|
||||
|
||||
for i := range matchingPaths {
|
||||
sceneID := i + 1
|
||||
expectedStudioID := studioID
|
||||
mockSceneReader.On("UpdatePartial", testCtx, sceneID, models.ScenePartial{
|
||||
mockSceneReader.On("UpdatePartial", mock.Anything, sceneID, models.ScenePartial{
|
||||
StudioID: models.NewOptionalInt(expectedStudioID),
|
||||
}).Return(nil, nil).Once()
|
||||
}
|
||||
|
||||
err := StudioScenes(testCtx, &studio, nil, aliases, mockSceneReader, nil)
|
||||
tagger := Tagger{
|
||||
TxnManager: &mocks.TxnManager{},
|
||||
}
|
||||
|
||||
err := tagger.StudioScenes(testCtx, &studio, nil, aliases, mockSceneReader)
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
|
@ -201,7 +210,9 @@ func testStudioImages(t *testing.T, tc testStudioCase) {
|
|||
}
|
||||
|
||||
organized := false
|
||||
perPage := models.PerPageAll
|
||||
perPage := 1000
|
||||
sort := "id"
|
||||
direction := models.SortDirectionEnumAsc
|
||||
|
||||
expectedImageFilter := &models.ImageFilterType{
|
||||
Organized: &organized,
|
||||
|
@ -212,11 +223,13 @@ func testStudioImages(t *testing.T, tc testStudioCase) {
|
|||
}
|
||||
|
||||
expectedFindFilter := &models.FindFilterType{
|
||||
PerPage: &perPage,
|
||||
PerPage: &perPage,
|
||||
Sort: &sort,
|
||||
Direction: &direction,
|
||||
}
|
||||
|
||||
// if alias provided, then don't find by name
|
||||
onNameQuery := mockImageReader.On("Query", testCtx, image.QueryOptions(expectedImageFilter, expectedFindFilter, false))
|
||||
onNameQuery := mockImageReader.On("Query", mock.Anything, image.QueryOptions(expectedImageFilter, expectedFindFilter, false))
|
||||
if aliasName == "" {
|
||||
onNameQuery.Return(mocks.ImageQueryResult(images, len(images)), nil).Once()
|
||||
} else {
|
||||
|
@ -230,19 +243,23 @@ func testStudioImages(t *testing.T, tc testStudioCase) {
|
|||
},
|
||||
}
|
||||
|
||||
mockImageReader.On("Query", testCtx, image.QueryOptions(expectedAliasFilter, expectedFindFilter, false)).
|
||||
mockImageReader.On("Query", mock.Anything, image.QueryOptions(expectedAliasFilter, expectedFindFilter, false)).
|
||||
Return(mocks.ImageQueryResult(images, len(images)), nil).Once()
|
||||
}
|
||||
|
||||
for i := range matchingPaths {
|
||||
imageID := i + 1
|
||||
expectedStudioID := studioID
|
||||
mockImageReader.On("UpdatePartial", testCtx, imageID, models.ImagePartial{
|
||||
mockImageReader.On("UpdatePartial", mock.Anything, imageID, models.ImagePartial{
|
||||
StudioID: models.NewOptionalInt(expectedStudioID),
|
||||
}).Return(nil, nil).Once()
|
||||
}
|
||||
|
||||
err := StudioImages(testCtx, &studio, nil, aliases, mockImageReader, nil)
|
||||
tagger := Tagger{
|
||||
TxnManager: &mocks.TxnManager{},
|
||||
}
|
||||
|
||||
err := tagger.StudioImages(testCtx, &studio, nil, aliases, mockImageReader)
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
|
@ -291,7 +308,9 @@ func testStudioGalleries(t *testing.T, tc testStudioCase) {
|
|||
}
|
||||
|
||||
organized := false
|
||||
perPage := models.PerPageAll
|
||||
perPage := 1000
|
||||
sort := "id"
|
||||
direction := models.SortDirectionEnumAsc
|
||||
|
||||
expectedGalleryFilter := &models.GalleryFilterType{
|
||||
Organized: &organized,
|
||||
|
@ -302,11 +321,13 @@ func testStudioGalleries(t *testing.T, tc testStudioCase) {
|
|||
}
|
||||
|
||||
expectedFindFilter := &models.FindFilterType{
|
||||
PerPage: &perPage,
|
||||
PerPage: &perPage,
|
||||
Sort: &sort,
|
||||
Direction: &direction,
|
||||
}
|
||||
|
||||
// if alias provided, then don't find by name
|
||||
onNameQuery := mockGalleryReader.On("Query", testCtx, expectedGalleryFilter, expectedFindFilter)
|
||||
onNameQuery := mockGalleryReader.On("Query", mock.Anything, expectedGalleryFilter, expectedFindFilter)
|
||||
if aliasName == "" {
|
||||
onNameQuery.Return(galleries, len(galleries), nil).Once()
|
||||
} else {
|
||||
|
@ -320,18 +341,22 @@ func testStudioGalleries(t *testing.T, tc testStudioCase) {
|
|||
},
|
||||
}
|
||||
|
||||
mockGalleryReader.On("Query", testCtx, expectedAliasFilter, expectedFindFilter).Return(galleries, len(galleries), nil).Once()
|
||||
mockGalleryReader.On("Query", mock.Anything, expectedAliasFilter, expectedFindFilter).Return(galleries, len(galleries), nil).Once()
|
||||
}
|
||||
|
||||
for i := range matchingPaths {
|
||||
galleryID := i + 1
|
||||
expectedStudioID := studioID
|
||||
mockGalleryReader.On("UpdatePartial", testCtx, galleryID, models.GalleryPartial{
|
||||
mockGalleryReader.On("UpdatePartial", mock.Anything, galleryID, models.GalleryPartial{
|
||||
StudioID: models.NewOptionalInt(expectedStudioID),
|
||||
}).Return(nil, nil).Once()
|
||||
}
|
||||
|
||||
err := StudioGalleries(testCtx, &studio, nil, aliases, mockGalleryReader, nil)
|
||||
tagger := Tagger{
|
||||
TxnManager: &mocks.TxnManager{},
|
||||
}
|
||||
|
||||
err := tagger.StudioGalleries(testCtx, &studio, nil, aliases, mockGalleryReader)
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/scene"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/intslice"
|
||||
"github.com/stashapp/stash/pkg/txn"
|
||||
)
|
||||
|
||||
type SceneQueryTagUpdater interface {
|
||||
|
@ -50,8 +51,8 @@ func getTagTaggers(p *models.Tag, aliases []string, cache *match.Cache) []tagger
|
|||
}
|
||||
|
||||
// TagScenes searches for scenes whose path matches the provided tag name and tags the scene with the tag.
|
||||
func TagScenes(ctx context.Context, p *models.Tag, paths []string, aliases []string, rw SceneQueryTagUpdater, cache *match.Cache) error {
|
||||
t := getTagTaggers(p, aliases, cache)
|
||||
func (tagger *Tagger) TagScenes(ctx context.Context, p *models.Tag, paths []string, aliases []string, rw SceneQueryTagUpdater) error {
|
||||
t := getTagTaggers(p, aliases, tagger.Cache)
|
||||
|
||||
for _, tt := range t {
|
||||
if err := tt.tagScenes(ctx, paths, rw, func(o *models.Scene) (bool, error) {
|
||||
|
@ -64,7 +65,9 @@ func TagScenes(ctx context.Context, p *models.Tag, paths []string, aliases []str
|
|||
return false, nil
|
||||
}
|
||||
|
||||
if err := scene.AddTag(ctx, rw, o, p.ID); err != nil {
|
||||
if err := txn.WithTxn(ctx, tagger.TxnManager, func(ctx context.Context) error {
|
||||
return scene.AddTag(ctx, rw, o, p.ID)
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
|
@ -77,8 +80,8 @@ func TagScenes(ctx context.Context, p *models.Tag, paths []string, aliases []str
|
|||
}
|
||||
|
||||
// TagImages searches for images whose path matches the provided tag name and tags the image with the tag.
|
||||
func TagImages(ctx context.Context, p *models.Tag, paths []string, aliases []string, rw ImageQueryTagUpdater, cache *match.Cache) error {
|
||||
t := getTagTaggers(p, aliases, cache)
|
||||
func (tagger *Tagger) TagImages(ctx context.Context, p *models.Tag, paths []string, aliases []string, rw ImageQueryTagUpdater) error {
|
||||
t := getTagTaggers(p, aliases, tagger.Cache)
|
||||
|
||||
for _, tt := range t {
|
||||
if err := tt.tagImages(ctx, paths, rw, func(o *models.Image) (bool, error) {
|
||||
|
@ -91,7 +94,9 @@ func TagImages(ctx context.Context, p *models.Tag, paths []string, aliases []str
|
|||
return false, nil
|
||||
}
|
||||
|
||||
if err := image.AddTag(ctx, rw, o, p.ID); err != nil {
|
||||
if err := txn.WithTxn(ctx, tagger.TxnManager, func(ctx context.Context) error {
|
||||
return image.AddTag(ctx, rw, o, p.ID)
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
|
@ -104,8 +109,8 @@ func TagImages(ctx context.Context, p *models.Tag, paths []string, aliases []str
|
|||
}
|
||||
|
||||
// TagGalleries searches for galleries whose path matches the provided tag name and tags the gallery with the tag.
|
||||
func TagGalleries(ctx context.Context, p *models.Tag, paths []string, aliases []string, rw GalleryQueryTagUpdater, cache *match.Cache) error {
|
||||
t := getTagTaggers(p, aliases, cache)
|
||||
func (tagger *Tagger) TagGalleries(ctx context.Context, p *models.Tag, paths []string, aliases []string, rw GalleryQueryTagUpdater) error {
|
||||
t := getTagTaggers(p, aliases, tagger.Cache)
|
||||
|
||||
for _, tt := range t {
|
||||
if err := tt.tagGalleries(ctx, paths, rw, func(o *models.Gallery) (bool, error) {
|
||||
|
@ -118,7 +123,9 @@ func TagGalleries(ctx context.Context, p *models.Tag, paths []string, aliases []
|
|||
return false, nil
|
||||
}
|
||||
|
||||
if err := gallery.AddTag(ctx, rw, o, p.ID); err != nil {
|
||||
if err := txn.WithTxn(ctx, tagger.TxnManager, func(ctx context.Context) error {
|
||||
return gallery.AddTag(ctx, rw, o, p.ID)
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/stashapp/stash/pkg/models/mocks"
|
||||
"github.com/stashapp/stash/pkg/scene"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type testTagCase struct {
|
||||
|
@ -111,7 +112,9 @@ func testTagScenes(t *testing.T, tc testTagCase) {
|
|||
}
|
||||
|
||||
organized := false
|
||||
perPage := models.PerPageAll
|
||||
perPage := 1000
|
||||
sort := "id"
|
||||
direction := models.SortDirectionEnumAsc
|
||||
|
||||
expectedSceneFilter := &models.SceneFilterType{
|
||||
Organized: &organized,
|
||||
|
@ -122,7 +125,9 @@ func testTagScenes(t *testing.T, tc testTagCase) {
|
|||
}
|
||||
|
||||
expectedFindFilter := &models.FindFilterType{
|
||||
PerPage: &perPage,
|
||||
PerPage: &perPage,
|
||||
Sort: &sort,
|
||||
Direction: &direction,
|
||||
}
|
||||
|
||||
// if alias provided, then don't find by name
|
||||
|
@ -140,13 +145,13 @@ func testTagScenes(t *testing.T, tc testTagCase) {
|
|||
},
|
||||
}
|
||||
|
||||
mockSceneReader.On("Query", testCtx, scene.QueryOptions(expectedAliasFilter, expectedFindFilter, false)).
|
||||
mockSceneReader.On("Query", mock.Anything, scene.QueryOptions(expectedAliasFilter, expectedFindFilter, false)).
|
||||
Return(mocks.SceneQueryResult(scenes, len(scenes)), nil).Once()
|
||||
}
|
||||
|
||||
for i := range matchingPaths {
|
||||
sceneID := i + 1
|
||||
mockSceneReader.On("UpdatePartial", testCtx, sceneID, models.ScenePartial{
|
||||
mockSceneReader.On("UpdatePartial", mock.Anything, sceneID, models.ScenePartial{
|
||||
TagIDs: &models.UpdateIDs{
|
||||
IDs: []int{tagID},
|
||||
Mode: models.RelationshipUpdateModeAdd,
|
||||
|
@ -154,7 +159,11 @@ func testTagScenes(t *testing.T, tc testTagCase) {
|
|||
}).Return(nil, nil).Once()
|
||||
}
|
||||
|
||||
err := TagScenes(testCtx, &tag, nil, aliases, mockSceneReader, nil)
|
||||
tagger := Tagger{
|
||||
TxnManager: &mocks.TxnManager{},
|
||||
}
|
||||
|
||||
err := tagger.TagScenes(testCtx, &tag, nil, aliases, mockSceneReader)
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
|
@ -204,7 +213,9 @@ func testTagImages(t *testing.T, tc testTagCase) {
|
|||
}
|
||||
|
||||
organized := false
|
||||
perPage := models.PerPageAll
|
||||
perPage := 1000
|
||||
sort := "id"
|
||||
direction := models.SortDirectionEnumAsc
|
||||
|
||||
expectedImageFilter := &models.ImageFilterType{
|
||||
Organized: &organized,
|
||||
|
@ -215,7 +226,9 @@ func testTagImages(t *testing.T, tc testTagCase) {
|
|||
}
|
||||
|
||||
expectedFindFilter := &models.FindFilterType{
|
||||
PerPage: &perPage,
|
||||
PerPage: &perPage,
|
||||
Sort: &sort,
|
||||
Direction: &direction,
|
||||
}
|
||||
|
||||
// if alias provided, then don't find by name
|
||||
|
@ -233,14 +246,14 @@ func testTagImages(t *testing.T, tc testTagCase) {
|
|||
},
|
||||
}
|
||||
|
||||
mockImageReader.On("Query", testCtx, image.QueryOptions(expectedAliasFilter, expectedFindFilter, false)).
|
||||
mockImageReader.On("Query", mock.Anything, image.QueryOptions(expectedAliasFilter, expectedFindFilter, false)).
|
||||
Return(mocks.ImageQueryResult(images, len(images)), nil).Once()
|
||||
}
|
||||
|
||||
for i := range matchingPaths {
|
||||
imageID := i + 1
|
||||
|
||||
mockImageReader.On("UpdatePartial", testCtx, imageID, models.ImagePartial{
|
||||
mockImageReader.On("UpdatePartial", mock.Anything, imageID, models.ImagePartial{
|
||||
TagIDs: &models.UpdateIDs{
|
||||
IDs: []int{tagID},
|
||||
Mode: models.RelationshipUpdateModeAdd,
|
||||
|
@ -248,7 +261,11 @@ func testTagImages(t *testing.T, tc testTagCase) {
|
|||
}).Return(nil, nil).Once()
|
||||
}
|
||||
|
||||
err := TagImages(testCtx, &tag, nil, aliases, mockImageReader, nil)
|
||||
tagger := Tagger{
|
||||
TxnManager: &mocks.TxnManager{},
|
||||
}
|
||||
|
||||
err := tagger.TagImages(testCtx, &tag, nil, aliases, mockImageReader)
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
|
@ -299,7 +316,9 @@ func testTagGalleries(t *testing.T, tc testTagCase) {
|
|||
}
|
||||
|
||||
organized := false
|
||||
perPage := models.PerPageAll
|
||||
perPage := 1000
|
||||
sort := "id"
|
||||
direction := models.SortDirectionEnumAsc
|
||||
|
||||
expectedGalleryFilter := &models.GalleryFilterType{
|
||||
Organized: &organized,
|
||||
|
@ -310,7 +329,9 @@ func testTagGalleries(t *testing.T, tc testTagCase) {
|
|||
}
|
||||
|
||||
expectedFindFilter := &models.FindFilterType{
|
||||
PerPage: &perPage,
|
||||
PerPage: &perPage,
|
||||
Sort: &sort,
|
||||
Direction: &direction,
|
||||
}
|
||||
|
||||
// if alias provided, then don't find by name
|
||||
|
@ -328,13 +349,13 @@ func testTagGalleries(t *testing.T, tc testTagCase) {
|
|||
},
|
||||
}
|
||||
|
||||
mockGalleryReader.On("Query", testCtx, expectedAliasFilter, expectedFindFilter).Return(galleries, len(galleries), nil).Once()
|
||||
mockGalleryReader.On("Query", mock.Anything, expectedAliasFilter, expectedFindFilter).Return(galleries, len(galleries), nil).Once()
|
||||
}
|
||||
|
||||
for i := range matchingPaths {
|
||||
galleryID := i + 1
|
||||
|
||||
mockGalleryReader.On("UpdatePartial", testCtx, galleryID, models.GalleryPartial{
|
||||
mockGalleryReader.On("UpdatePartial", mock.Anything, galleryID, models.GalleryPartial{
|
||||
TagIDs: &models.UpdateIDs{
|
||||
IDs: []int{tagID},
|
||||
Mode: models.RelationshipUpdateModeAdd,
|
||||
|
@ -343,7 +364,11 @@ func testTagGalleries(t *testing.T, tc testTagCase) {
|
|||
|
||||
}
|
||||
|
||||
err := TagGalleries(testCtx, &tag, nil, aliases, mockGalleryReader, nil)
|
||||
tagger := Tagger{
|
||||
TxnManager: &mocks.TxnManager{},
|
||||
}
|
||||
|
||||
err := tagger.TagGalleries(testCtx, &tag, nil, aliases, mockGalleryReader)
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
|
|
|
@ -23,8 +23,14 @@ import (
|
|||
"github.com/stashapp/stash/pkg/match"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/scene"
|
||||
"github.com/stashapp/stash/pkg/txn"
|
||||
)
|
||||
|
||||
type Tagger struct {
|
||||
TxnManager txn.Manager
|
||||
Cache *match.Cache
|
||||
}
|
||||
|
||||
type tagger struct {
|
||||
ID int
|
||||
Type string
|
||||
|
@ -112,12 +118,7 @@ func (t *tagger) tagTags(ctx context.Context, tagReader match.TagAutoTagQueryer,
|
|||
}
|
||||
|
||||
func (t *tagger) tagScenes(ctx context.Context, paths []string, sceneReader scene.Queryer, addFunc addSceneLinkFunc) error {
|
||||
others, err := match.PathToScenes(ctx, t.Name, paths, sceneReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, p := range others {
|
||||
return match.PathToScenesFn(ctx, t.Name, paths, sceneReader, func(ctx context.Context, p *models.Scene) error {
|
||||
added, err := addFunc(p)
|
||||
|
||||
if err != nil {
|
||||
|
@ -127,18 +128,13 @@ func (t *tagger) tagScenes(ctx context.Context, paths []string, sceneReader scen
|
|||
if added {
|
||||
t.addLog("scene", p.DisplayName())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (t *tagger) tagImages(ctx context.Context, paths []string, imageReader image.Queryer, addFunc addImageLinkFunc) error {
|
||||
others, err := match.PathToImages(ctx, t.Name, paths, imageReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, p := range others {
|
||||
return match.PathToImagesFn(ctx, t.Name, paths, imageReader, func(ctx context.Context, p *models.Image) error {
|
||||
added, err := addFunc(p)
|
||||
|
||||
if err != nil {
|
||||
|
@ -148,18 +144,13 @@ func (t *tagger) tagImages(ctx context.Context, paths []string, imageReader imag
|
|||
if added {
|
||||
t.addLog("image", p.DisplayName())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (t *tagger) tagGalleries(ctx context.Context, paths []string, galleryReader gallery.Queryer, addFunc addGalleryLinkFunc) error {
|
||||
others, err := match.PathToGalleries(ctx, t.Name, paths, galleryReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, p := range others {
|
||||
return match.PathToGalleriesFn(ctx, t.Name, paths, galleryReader, func(ctx context.Context, p *models.Gallery) error {
|
||||
added, err := addFunc(p)
|
||||
|
||||
if err != nil {
|
||||
|
@ -169,7 +160,7 @@ func (t *tagger) tagGalleries(ctx context.Context, paths []string, galleryReader
|
|||
if added {
|
||||
t.addLog("gallery", p.DisplayName())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
|
|
@ -121,6 +121,11 @@ func (j *autoTagJob) autoTagPerformers(ctx context.Context, progress *job.Progre
|
|||
return
|
||||
}
|
||||
|
||||
tagger := autotag.Tagger{
|
||||
TxnManager: j.txnManager,
|
||||
Cache: &j.cache,
|
||||
}
|
||||
|
||||
for _, performerId := range performerIds {
|
||||
var performers []*models.Performer
|
||||
|
||||
|
@ -162,20 +167,20 @@ func (j *autoTagJob) autoTagPerformers(ctx context.Context, progress *job.Progre
|
|||
return nil
|
||||
}
|
||||
|
||||
if err := j.txnManager.WithTxn(ctx, func(ctx context.Context) error {
|
||||
if err := func() error {
|
||||
r := j.txnManager
|
||||
if err := autotag.PerformerScenes(ctx, performer, paths, r.Scene, &j.cache); err != nil {
|
||||
if err := tagger.PerformerScenes(ctx, performer, paths, r.Scene); err != nil {
|
||||
return fmt.Errorf("processing scenes: %w", err)
|
||||
}
|
||||
if err := autotag.PerformerImages(ctx, performer, paths, r.Image, &j.cache); err != nil {
|
||||
if err := tagger.PerformerImages(ctx, performer, paths, r.Image); err != nil {
|
||||
return fmt.Errorf("processing images: %w", err)
|
||||
}
|
||||
if err := autotag.PerformerGalleries(ctx, performer, paths, r.Gallery, &j.cache); err != nil {
|
||||
if err := tagger.PerformerGalleries(ctx, performer, paths, r.Gallery); err != nil {
|
||||
return fmt.Errorf("processing galleries: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
}(); err != nil {
|
||||
return fmt.Errorf("error auto-tagging performer '%s': %s", performer.Name, err.Error())
|
||||
}
|
||||
|
||||
|
@ -196,6 +201,10 @@ func (j *autoTagJob) autoTagStudios(ctx context.Context, progress *job.Progress,
|
|||
}
|
||||
|
||||
r := j.txnManager
|
||||
tagger := autotag.Tagger{
|
||||
TxnManager: j.txnManager,
|
||||
Cache: &j.cache,
|
||||
}
|
||||
|
||||
for _, studioId := range studioIds {
|
||||
var studios []*models.Studio
|
||||
|
@ -238,24 +247,24 @@ func (j *autoTagJob) autoTagStudios(ctx context.Context, progress *job.Progress,
|
|||
return nil
|
||||
}
|
||||
|
||||
if err := j.txnManager.WithTxn(ctx, func(ctx context.Context) error {
|
||||
if err := func() error {
|
||||
aliases, err := r.Studio.GetAliases(ctx, studio.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting studio aliases: %w", err)
|
||||
}
|
||||
|
||||
if err := autotag.StudioScenes(ctx, studio, paths, aliases, r.Scene, &j.cache); err != nil {
|
||||
if err := tagger.StudioScenes(ctx, studio, paths, aliases, r.Scene); err != nil {
|
||||
return fmt.Errorf("processing scenes: %w", err)
|
||||
}
|
||||
if err := autotag.StudioImages(ctx, studio, paths, aliases, r.Image, &j.cache); err != nil {
|
||||
if err := tagger.StudioImages(ctx, studio, paths, aliases, r.Image); err != nil {
|
||||
return fmt.Errorf("processing images: %w", err)
|
||||
}
|
||||
if err := autotag.StudioGalleries(ctx, studio, paths, aliases, r.Gallery, &j.cache); err != nil {
|
||||
if err := tagger.StudioGalleries(ctx, studio, paths, aliases, r.Gallery); err != nil {
|
||||
return fmt.Errorf("processing galleries: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
}(); err != nil {
|
||||
return fmt.Errorf("error auto-tagging studio '%s': %s", studio.Name.String, err.Error())
|
||||
}
|
||||
|
||||
|
@ -276,6 +285,10 @@ func (j *autoTagJob) autoTagTags(ctx context.Context, progress *job.Progress, pa
|
|||
}
|
||||
|
||||
r := j.txnManager
|
||||
tagger := autotag.Tagger{
|
||||
TxnManager: j.txnManager,
|
||||
Cache: &j.cache,
|
||||
}
|
||||
|
||||
for _, tagId := range tagIds {
|
||||
var tags []*models.Tag
|
||||
|
@ -312,24 +325,24 @@ func (j *autoTagJob) autoTagTags(ctx context.Context, progress *job.Progress, pa
|
|||
return nil
|
||||
}
|
||||
|
||||
if err := j.txnManager.WithTxn(ctx, func(ctx context.Context) error {
|
||||
if err := func() error {
|
||||
aliases, err := r.Tag.GetAliases(ctx, tag.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting tag aliases: %w", err)
|
||||
}
|
||||
|
||||
if err := autotag.TagScenes(ctx, tag, paths, aliases, r.Scene, &j.cache); err != nil {
|
||||
if err := tagger.TagScenes(ctx, tag, paths, aliases, r.Scene); err != nil {
|
||||
return fmt.Errorf("processing scenes: %w", err)
|
||||
}
|
||||
if err := autotag.TagImages(ctx, tag, paths, aliases, r.Image, &j.cache); err != nil {
|
||||
if err := tagger.TagImages(ctx, tag, paths, aliases, r.Image); err != nil {
|
||||
return fmt.Errorf("processing images: %w", err)
|
||||
}
|
||||
if err := autotag.TagGalleries(ctx, tag, paths, aliases, r.Gallery, &j.cache); err != nil {
|
||||
if err := tagger.TagGalleries(ctx, tag, paths, aliases, r.Gallery); err != nil {
|
||||
return fmt.Errorf("processing galleries: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
}(); err != nil {
|
||||
return fmt.Errorf("error auto-tagging tag '%s': %s", tag.Name, err.Error())
|
||||
}
|
||||
|
||||
|
|
|
@ -278,7 +278,7 @@ func PathToTags(ctx context.Context, path string, reader TagAutoTagQueryer, cach
|
|||
return ret, nil
|
||||
}
|
||||
|
||||
func PathToScenes(ctx context.Context, name string, paths []string, sceneReader scene.Queryer) ([]*models.Scene, error) {
|
||||
func PathToScenesFn(ctx context.Context, name string, paths []string, sceneReader scene.Queryer, fn func(ctx context.Context, scene *models.Scene) error) error {
|
||||
regex := getPathQueryRegex(name)
|
||||
organized := false
|
||||
filter := models.SceneFilterType{
|
||||
|
@ -291,31 +291,53 @@ func PathToScenes(ctx context.Context, name string, paths []string, sceneReader
|
|||
|
||||
filter.And = scene.PathsFilter(paths)
|
||||
|
||||
pp := models.PerPageAll
|
||||
scenes, err := scene.Query(ctx, sceneReader, &filter, &models.FindFilterType{
|
||||
PerPage: &pp,
|
||||
})
|
||||
// do in batches
|
||||
pp := 1000
|
||||
sort := "id"
|
||||
sortDir := models.SortDirectionEnumAsc
|
||||
lastID := 0
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error querying scenes with regex '%s': %s", regex, err.Error())
|
||||
}
|
||||
|
||||
var ret []*models.Scene
|
||||
|
||||
// paths may have unicode characters
|
||||
const useUnicode = true
|
||||
|
||||
r := nameToRegexp(name, useUnicode)
|
||||
for _, p := range scenes {
|
||||
if regexpMatchesPath(r, p.Path) != -1 {
|
||||
ret = append(ret, p)
|
||||
for {
|
||||
if lastID != 0 {
|
||||
filter.ID = &models.IntCriterionInput{
|
||||
Value: lastID,
|
||||
Modifier: models.CriterionModifierGreaterThan,
|
||||
}
|
||||
}
|
||||
|
||||
scenes, err := scene.Query(ctx, sceneReader, &filter, &models.FindFilterType{
|
||||
PerPage: &pp,
|
||||
Sort: &sort,
|
||||
Direction: &sortDir,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error querying scenes with regex '%s': %s", regex, err.Error())
|
||||
}
|
||||
|
||||
// paths may have unicode characters
|
||||
const useUnicode = true
|
||||
|
||||
r := nameToRegexp(name, useUnicode)
|
||||
for _, p := range scenes {
|
||||
if regexpMatchesPath(r, p.Path) != -1 {
|
||||
if err := fn(ctx, p); err != nil {
|
||||
return fmt.Errorf("processing scene %s: %w", p.GetTitle(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(scenes) < pp {
|
||||
break
|
||||
}
|
||||
|
||||
lastID = scenes[len(scenes)-1].ID
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func PathToImages(ctx context.Context, name string, paths []string, imageReader image.Queryer) ([]*models.Image, error) {
|
||||
func PathToImagesFn(ctx context.Context, name string, paths []string, imageReader image.Queryer, fn func(ctx context.Context, scene *models.Image) error) error {
|
||||
regex := getPathQueryRegex(name)
|
||||
organized := false
|
||||
filter := models.ImageFilterType{
|
||||
|
@ -328,31 +350,53 @@ func PathToImages(ctx context.Context, name string, paths []string, imageReader
|
|||
|
||||
filter.And = image.PathsFilter(paths)
|
||||
|
||||
pp := models.PerPageAll
|
||||
images, err := image.Query(ctx, imageReader, &filter, &models.FindFilterType{
|
||||
PerPage: &pp,
|
||||
})
|
||||
// do in batches
|
||||
pp := 1000
|
||||
sort := "id"
|
||||
sortDir := models.SortDirectionEnumAsc
|
||||
lastID := 0
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error querying images with regex '%s': %s", regex, err.Error())
|
||||
}
|
||||
|
||||
var ret []*models.Image
|
||||
|
||||
// paths may have unicode characters
|
||||
const useUnicode = true
|
||||
|
||||
r := nameToRegexp(name, useUnicode)
|
||||
for _, p := range images {
|
||||
if regexpMatchesPath(r, p.Path) != -1 {
|
||||
ret = append(ret, p)
|
||||
for {
|
||||
if lastID != 0 {
|
||||
filter.ID = &models.IntCriterionInput{
|
||||
Value: lastID,
|
||||
Modifier: models.CriterionModifierGreaterThan,
|
||||
}
|
||||
}
|
||||
|
||||
images, err := image.Query(ctx, imageReader, &filter, &models.FindFilterType{
|
||||
PerPage: &pp,
|
||||
Sort: &sort,
|
||||
Direction: &sortDir,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error querying images with regex '%s': %s", regex, err.Error())
|
||||
}
|
||||
|
||||
// paths may have unicode characters
|
||||
const useUnicode = true
|
||||
|
||||
r := nameToRegexp(name, useUnicode)
|
||||
for _, p := range images {
|
||||
if regexpMatchesPath(r, p.Path) != -1 {
|
||||
if err := fn(ctx, p); err != nil {
|
||||
return fmt.Errorf("processing image %s: %w", p.GetTitle(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(images) < pp {
|
||||
break
|
||||
}
|
||||
|
||||
lastID = images[len(images)-1].ID
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func PathToGalleries(ctx context.Context, name string, paths []string, galleryReader gallery.Queryer) ([]*models.Gallery, error) {
|
||||
func PathToGalleriesFn(ctx context.Context, name string, paths []string, galleryReader gallery.Queryer, fn func(ctx context.Context, scene *models.Gallery) error) error {
|
||||
regex := getPathQueryRegex(name)
|
||||
organized := false
|
||||
filter := models.GalleryFilterType{
|
||||
|
@ -365,27 +409,49 @@ func PathToGalleries(ctx context.Context, name string, paths []string, galleryRe
|
|||
|
||||
filter.And = gallery.PathsFilter(paths)
|
||||
|
||||
pp := models.PerPageAll
|
||||
gallerys, _, err := galleryReader.Query(ctx, &filter, &models.FindFilterType{
|
||||
PerPage: &pp,
|
||||
})
|
||||
// do in batches
|
||||
pp := 1000
|
||||
sort := "id"
|
||||
sortDir := models.SortDirectionEnumAsc
|
||||
lastID := 0
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error querying gallerys with regex '%s': %s", regex, err.Error())
|
||||
}
|
||||
|
||||
var ret []*models.Gallery
|
||||
|
||||
// paths may have unicode characters
|
||||
const useUnicode = true
|
||||
|
||||
r := nameToRegexp(name, useUnicode)
|
||||
for _, p := range gallerys {
|
||||
path := p.Path
|
||||
if path != "" && regexpMatchesPath(r, path) != -1 {
|
||||
ret = append(ret, p)
|
||||
for {
|
||||
if lastID != 0 {
|
||||
filter.ID = &models.IntCriterionInput{
|
||||
Value: lastID,
|
||||
Modifier: models.CriterionModifierGreaterThan,
|
||||
}
|
||||
}
|
||||
|
||||
galleries, _, err := galleryReader.Query(ctx, &filter, &models.FindFilterType{
|
||||
PerPage: &pp,
|
||||
Sort: &sort,
|
||||
Direction: &sortDir,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error querying galleries with regex '%s': %s", regex, err.Error())
|
||||
}
|
||||
|
||||
// paths may have unicode characters
|
||||
const useUnicode = true
|
||||
|
||||
r := nameToRegexp(name, useUnicode)
|
||||
for _, p := range galleries {
|
||||
path := p.Path
|
||||
if path != "" && regexpMatchesPath(r, path) != -1 {
|
||||
if err := fn(ctx, p); err != nil {
|
||||
return fmt.Errorf("processing gallery %s: %w", p.GetTitle(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(galleries) < pp {
|
||||
break
|
||||
}
|
||||
|
||||
lastID = galleries[len(galleries)-1].ID
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ type GalleryFilterType struct {
|
|||
And *GalleryFilterType `json:"AND"`
|
||||
Or *GalleryFilterType `json:"OR"`
|
||||
Not *GalleryFilterType `json:"NOT"`
|
||||
ID *IntCriterionInput `json:"id"`
|
||||
Title *StringCriterionInput `json:"title"`
|
||||
Details *StringCriterionInput `json:"details"`
|
||||
// Filter by file checksum
|
||||
|
|
|
@ -6,6 +6,7 @@ type ImageFilterType struct {
|
|||
And *ImageFilterType `json:"AND"`
|
||||
Or *ImageFilterType `json:"OR"`
|
||||
Not *ImageFilterType `json:"NOT"`
|
||||
ID *IntCriterionInput `json:"id"`
|
||||
Title *StringCriterionInput `json:"title"`
|
||||
// Filter by file checksum
|
||||
Checksum *StringCriterionInput `json:"checksum"`
|
||||
|
|
|
@ -16,6 +16,7 @@ type SceneFilterType struct {
|
|||
And *SceneFilterType `json:"AND"`
|
||||
Or *SceneFilterType `json:"OR"`
|
||||
Not *SceneFilterType `json:"NOT"`
|
||||
ID *IntCriterionInput `json:"id"`
|
||||
Title *StringCriterionInput `json:"title"`
|
||||
Code *StringCriterionInput `json:"code"`
|
||||
Details *StringCriterionInput `json:"details"`
|
||||
|
|
|
@ -624,6 +624,7 @@ func (qb *GalleryStore) makeFilter(ctx context.Context, galleryFilter *models.Ga
|
|||
query.not(qb.makeFilter(ctx, galleryFilter.Not))
|
||||
}
|
||||
|
||||
query.handleCriterion(ctx, intCriterionHandler(galleryFilter.ID, "galleries.id", nil))
|
||||
query.handleCriterion(ctx, stringCriterionHandler(galleryFilter.Title, "galleries.title"))
|
||||
query.handleCriterion(ctx, stringCriterionHandler(galleryFilter.Details, "galleries.details"))
|
||||
|
||||
|
|
|
@ -619,6 +619,7 @@ func (qb *ImageStore) makeFilter(ctx context.Context, imageFilter *models.ImageF
|
|||
query.not(qb.makeFilter(ctx, imageFilter.Not))
|
||||
}
|
||||
|
||||
query.handleCriterion(ctx, intCriterionHandler(imageFilter.ID, "images.id", nil))
|
||||
query.handleCriterion(ctx, criterionHandlerFunc(func(ctx context.Context, f *filterBuilder) {
|
||||
if imageFilter.Checksum != nil {
|
||||
qb.addImagesFilesTable(f)
|
||||
|
|
|
@ -806,6 +806,7 @@ func (qb *SceneStore) makeFilter(ctx context.Context, sceneFilter *models.SceneF
|
|||
query.not(qb.makeFilter(ctx, sceneFilter.Not))
|
||||
}
|
||||
|
||||
query.handleCriterion(ctx, intCriterionHandler(sceneFilter.ID, "scenes.id", nil))
|
||||
query.handleCriterion(ctx, pathCriterionHandler(sceneFilter.Path, "folders.path", "files.basename", qb.addFoldersTable))
|
||||
query.handleCriterion(ctx, sceneFileCountCriterionHandler(qb, sceneFilter.FileCount))
|
||||
query.handleCriterion(ctx, stringCriterionHandler(sceneFilter.Title, "scenes.title"))
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
* Changed Performer height to be numeric, and changed filtering accordingly. ([#3060](https://github.com/stashapp/stash/pull/3060))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
* Fixed autotag error when tagging a large amount of objects. ([#3106](https://github.com/stashapp/stash/pull/3106))
|
||||
* Fixed Gallery title being incorrectly marked as mandatory for file- and folder-based galleries. ([#3110](https://github.com/stashapp/stash/pull/3110))
|
||||
* Fixed Saved Filters not ordered by name. ([#3101](https://github.com/stashapp/stash/pull/3101))
|
||||
* Scene Player no longer always resumes playing when seeking. ([#3020](https://github.com/stashapp/stash/pull/3020))
|
||||
|
|
Loading…
Reference in New Issue