diff --git a/graphql/documents/data/performer-slim.graphql b/graphql/documents/data/performer-slim.graphql index cafe7614d..1dd692a6e 100644 --- a/graphql/documents/data/performer-slim.graphql +++ b/graphql/documents/data/performer-slim.graphql @@ -7,6 +7,7 @@ fragment SlimPerformerData on Performer { instagram image_path favorite + ignore_auto_tag country birthdate ethnicity diff --git a/graphql/documents/data/performer.graphql b/graphql/documents/data/performer.graphql index 34ff0279d..4030d6697 100644 --- a/graphql/documents/data/performer.graphql +++ b/graphql/documents/data/performer.graphql @@ -18,6 +18,7 @@ fragment PerformerData on Performer { piercings aliases favorite + ignore_auto_tag image_path scene_count image_count diff --git a/graphql/documents/data/studio.graphql b/graphql/documents/data/studio.graphql index a252ce2f3..94e118b6b 100644 --- a/graphql/documents/data/studio.graphql +++ b/graphql/documents/data/studio.graphql @@ -14,6 +14,7 @@ fragment StudioData on Studio { name image_path } + ignore_auto_tag image_path scene_count image_count diff --git a/graphql/documents/data/tag.graphql b/graphql/documents/data/tag.graphql index ab83adf73..336d3a9c8 100644 --- a/graphql/documents/data/tag.graphql +++ b/graphql/documents/data/tag.graphql @@ -2,6 +2,7 @@ fragment TagData on Tag { id name aliases + ignore_auto_tag image_path scene_count scene_marker_count diff --git a/graphql/schema/types/filters.graphql b/graphql/schema/types/filters.graphql index bf3517fae..58c6d72fd 100644 --- a/graphql/schema/types/filters.graphql +++ b/graphql/schema/types/filters.graphql @@ -101,6 +101,8 @@ input PerformerFilterType { death_year: IntCriterionInput """Filter by studios where performer appears in scene/image/gallery""" studios: HierarchicalMultiCriterionInput + """Filter by autotag ignore value""" + ignore_auto_tag: Boolean } input SceneMarkerFilterType { @@ -219,6 +221,8 @@ input StudioFilterType { url: StringCriterionInput """Filter by studio aliases""" aliases: StringCriterionInput + """Filter by autotag ignore value""" + ignore_auto_tag: Boolean } input GalleryFilterType { @@ -305,6 +309,9 @@ input TagFilterType { """Filter by number f child tags the tag has""" child_count: IntCriterionInput + + """Filter by autotag ignore value""" + ignore_auto_tag: Boolean } input ImageFilterType { diff --git a/graphql/schema/types/performer.graphql b/graphql/schema/types/performer.graphql index 8c0c6e396..e69d52e47 100644 --- a/graphql/schema/types/performer.graphql +++ b/graphql/schema/types/performer.graphql @@ -28,6 +28,7 @@ type Performer { aliases: String favorite: Boolean! tags: [Tag!]! + ignore_auto_tag: Boolean! image_path: String # Resolver scene_count: Int # Resolver @@ -73,6 +74,7 @@ input PerformerCreateInput { death_date: String hair_color: String weight: Int + ignore_auto_tag: Boolean } input PerformerUpdateInput { @@ -103,6 +105,7 @@ input PerformerUpdateInput { death_date: String hair_color: String weight: Int + ignore_auto_tag: Boolean } input BulkPerformerUpdateInput { @@ -130,6 +133,7 @@ input BulkPerformerUpdateInput { death_date: String hair_color: String weight: Int + ignore_auto_tag: Boolean } input PerformerDestroyInput { diff --git a/graphql/schema/types/studio.graphql b/graphql/schema/types/studio.graphql index 183ffc5f6..7bf4bb355 100644 --- a/graphql/schema/types/studio.graphql +++ b/graphql/schema/types/studio.graphql @@ -6,6 +6,7 @@ type Studio { parent_studio: Studio child_studios: [Studio!]! aliases: [String!]! + ignore_auto_tag: Boolean! image_path: String # Resolver scene_count: Int # Resolver @@ -30,6 +31,7 @@ input StudioCreateInput { rating: Int details: String aliases: [String!] + ignore_auto_tag: Boolean } input StudioUpdateInput { @@ -43,6 +45,7 @@ input StudioUpdateInput { rating: Int details: String aliases: [String!] + ignore_auto_tag: Boolean } input StudioDestroyInput { diff --git a/graphql/schema/types/tag.graphql b/graphql/schema/types/tag.graphql index fea9f40fe..537fab8b9 100644 --- a/graphql/schema/types/tag.graphql +++ b/graphql/schema/types/tag.graphql @@ -2,6 +2,7 @@ type Tag { id: ID! name: String! aliases: [String!]! + ignore_auto_tag: Boolean! created_at: Time! updated_at: Time! @@ -19,6 +20,7 @@ type Tag { input TagCreateInput { name: String! aliases: [String!] + ignore_auto_tag: Boolean """This should be a URL or a base64 encoded data URL""" image: String @@ -31,6 +33,7 @@ input TagUpdateInput { id: ID! name: String aliases: [String!] + ignore_auto_tag: Boolean """This should be a URL or a base64 encoded data URL""" image: String diff --git a/internal/api/resolver_mutation_performer.go b/internal/api/resolver_mutation_performer.go index 55f83ac31..cf6335659 100644 --- a/internal/api/resolver_mutation_performer.go +++ b/internal/api/resolver_mutation_performer.go @@ -117,6 +117,9 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.Per weight := int64(*input.Weight) newPerformer.Weight = sql.NullInt64{Int64: weight, Valid: true} } + if input.IgnoreAutoTag != nil { + newPerformer.IgnoreAutoTag = *input.IgnoreAutoTag + } if err := performer.ValidateDeathDate(nil, input.Birthdate, input.DeathDate); err != nil { if err != nil { @@ -223,6 +226,7 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.Per updatedPerformer.DeathDate = translator.sqliteDate(input.DeathDate, "death_date") updatedPerformer.HairColor = translator.nullString(input.HairColor, "hair_color") updatedPerformer.Weight = translator.nullInt64(input.Weight, "weight") + updatedPerformer.IgnoreAutoTag = input.IgnoreAutoTag // Start the transaction and save the p var p *models.Performer @@ -331,6 +335,7 @@ func (r *mutationResolver) BulkPerformerUpdate(ctx context.Context, input models updatedPerformer.DeathDate = translator.sqliteDate(input.DeathDate, "death_date") updatedPerformer.HairColor = translator.nullString(input.HairColor, "hair_color") updatedPerformer.Weight = translator.nullInt64(input.Weight, "weight") + updatedPerformer.IgnoreAutoTag = input.IgnoreAutoTag if translator.hasField("gender") { if input.Gender != nil { diff --git a/internal/api/resolver_mutation_studio.go b/internal/api/resolver_mutation_studio.go index a60e29516..23f2a9bdc 100644 --- a/internal/api/resolver_mutation_studio.go +++ b/internal/api/resolver_mutation_studio.go @@ -66,6 +66,9 @@ func (r *mutationResolver) StudioCreate(ctx context.Context, input models.Studio if input.Details != nil { newStudio.Details = sql.NullString{String: *input.Details, Valid: true} } + if input.IgnoreAutoTag != nil { + newStudio.IgnoreAutoTag = *input.IgnoreAutoTag + } // Start the transaction and save the studio var s *models.Studio @@ -148,6 +151,7 @@ func (r *mutationResolver) StudioUpdate(ctx context.Context, input models.Studio updatedStudio.Details = translator.nullString(input.Details, "details") updatedStudio.ParentID = translator.nullInt64FromString(input.ParentID, "parent_id") updatedStudio.Rating = translator.nullInt64(input.Rating, "rating") + updatedStudio.IgnoreAutoTag = input.IgnoreAutoTag // Start the transaction and save the studio var s *models.Studio diff --git a/internal/api/resolver_mutation_tag.go b/internal/api/resolver_mutation_tag.go index dc806492b..680479b8d 100644 --- a/internal/api/resolver_mutation_tag.go +++ b/internal/api/resolver_mutation_tag.go @@ -34,6 +34,10 @@ func (r *mutationResolver) TagCreate(ctx context.Context, input models.TagCreate UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime}, } + if input.IgnoreAutoTag != nil { + newTag.IgnoreAutoTag = *input.IgnoreAutoTag + } + var imageData []byte var err error @@ -178,8 +182,9 @@ func (r *mutationResolver) TagUpdate(ctx context.Context, input models.TagUpdate } updatedTag := models.TagPartial{ - ID: tagID, - UpdatedAt: &models.SQLiteTimestamp{Timestamp: time.Now()}, + ID: tagID, + IgnoreAutoTag: input.IgnoreAutoTag, + UpdatedAt: &models.SQLiteTimestamp{Timestamp: time.Now()}, } if input.Name != nil && t.Name != *input.Name { diff --git a/internal/autotag/integration_test.go b/internal/autotag/integration_test.go index 65f118303..9ca176d4b 100644 --- a/internal/autotag/integration_test.go +++ b/internal/autotag/integration_test.go @@ -50,7 +50,9 @@ func runTests(m *testing.M) int { f.Close() databaseFile := f.Name() - database.Initialize(databaseFile) + if err := database.Initialize(databaseFile); err != nil { + panic(fmt.Sprintf("Could not initialize database: %s", err.Error())) + } // defer close and delete the database defer testTeardown(databaseFile) diff --git a/internal/manager/task_autotag.go b/internal/manager/task_autotag.go index 0a5610722..cdb330bba 100644 --- a/internal/manager/task_autotag.go +++ b/internal/manager/task_autotag.go @@ -126,10 +126,16 @@ func (j *autoTagJob) autoTagPerformers(ctx context.Context, progress *job.Progre if err := j.txnManager.WithReadTxn(context.TODO(), func(r models.ReaderRepository) error { performerQuery := r.Performer() + ignoreAutoTag := false + perPage := -1 if performerId == "*" { var err error - performers, err = performerQuery.All() + performers, _, err = performerQuery.Query(&models.PerformerFilterType{ + IgnoreAutoTag: &ignoreAutoTag, + }, &models.FindFilterType{ + PerPage: &perPage, + }) if err != nil { return fmt.Errorf("error querying performers: %v", err) } @@ -193,9 +199,15 @@ func (j *autoTagJob) autoTagStudios(ctx context.Context, progress *job.Progress, if err := j.txnManager.WithReadTxn(context.TODO(), func(r models.ReaderRepository) error { studioQuery := r.Studio() + ignoreAutoTag := false + perPage := -1 if studioId == "*" { var err error - studios, err = studioQuery.All() + studios, _, err = studioQuery.Query(&models.StudioFilterType{ + IgnoreAutoTag: &ignoreAutoTag, + }, &models.FindFilterType{ + PerPage: &perPage, + }) if err != nil { return fmt.Errorf("error querying studios: %v", err) } @@ -264,9 +276,15 @@ func (j *autoTagJob) autoTagTags(ctx context.Context, progress *job.Progress, pa var tags []*models.Tag if err := j.txnManager.WithReadTxn(context.TODO(), func(r models.ReaderRepository) error { tagQuery := r.Tag() + ignoreAutoTag := false + perPage := -1 if tagId == "*" { var err error - tags, err = tagQuery.All() + tags, _, err = tagQuery.Query(&models.TagFilterType{ + IgnoreAutoTag: &ignoreAutoTag, + }, &models.FindFilterType{ + PerPage: &perPage, + }) if err != nil { return fmt.Errorf("error querying tags: %v", err) } diff --git a/pkg/database/database.go b/pkg/database/database.go index 3300b11c9..457b81797 100644 --- a/pkg/database/database.go +++ b/pkg/database/database.go @@ -23,7 +23,7 @@ import ( var DB *sqlx.DB var WriteMu sync.Mutex var dbPath string -var appSchemaVersion uint = 29 +var appSchemaVersion uint = 30 var databaseSchemaVersion uint //go:embed migrations/*.sql diff --git a/pkg/database/migrations/30_ignore_autotag.up..sql b/pkg/database/migrations/30_ignore_autotag.up..sql new file mode 100644 index 000000000..44697f955 --- /dev/null +++ b/pkg/database/migrations/30_ignore_autotag.up..sql @@ -0,0 +1,3 @@ +ALTER TABLE `performers` ADD COLUMN `ignore_auto_tag` boolean not null default '0'; +ALTER TABLE `studios` ADD COLUMN `ignore_auto_tag` boolean not null default '0'; +ALTER TABLE `tags` ADD COLUMN `ignore_auto_tag` boolean not null default '0'; \ No newline at end of file diff --git a/pkg/models/jsonschema/performer.go b/pkg/models/jsonschema/performer.go index 6fee26a18..e7901b760 100644 --- a/pkg/models/jsonschema/performer.go +++ b/pkg/models/jsonschema/performer.go @@ -9,33 +9,34 @@ import ( ) type Performer struct { - Name string `json:"name,omitempty"` - Gender string `json:"gender,omitempty"` - URL string `json:"url,omitempty"` - Twitter string `json:"twitter,omitempty"` - Instagram string `json:"instagram,omitempty"` - Birthdate string `json:"birthdate,omitempty"` - Ethnicity string `json:"ethnicity,omitempty"` - Country string `json:"country,omitempty"` - EyeColor string `json:"eye_color,omitempty"` - Height string `json:"height,omitempty"` - Measurements string `json:"measurements,omitempty"` - FakeTits string `json:"fake_tits,omitempty"` - CareerLength string `json:"career_length,omitempty"` - Tattoos string `json:"tattoos,omitempty"` - Piercings string `json:"piercings,omitempty"` - Aliases string `json:"aliases,omitempty"` - Favorite bool `json:"favorite,omitempty"` - Tags []string `json:"tags,omitempty"` - Image string `json:"image,omitempty"` - CreatedAt models.JSONTime `json:"created_at,omitempty"` - UpdatedAt models.JSONTime `json:"updated_at,omitempty"` - Rating int `json:"rating,omitempty"` - Details string `json:"details,omitempty"` - DeathDate string `json:"death_date,omitempty"` - HairColor string `json:"hair_color,omitempty"` - Weight int `json:"weight,omitempty"` - StashIDs []models.StashID `json:"stash_ids,omitempty"` + Name string `json:"name,omitempty"` + Gender string `json:"gender,omitempty"` + URL string `json:"url,omitempty"` + Twitter string `json:"twitter,omitempty"` + Instagram string `json:"instagram,omitempty"` + Birthdate string `json:"birthdate,omitempty"` + Ethnicity string `json:"ethnicity,omitempty"` + Country string `json:"country,omitempty"` + EyeColor string `json:"eye_color,omitempty"` + Height string `json:"height,omitempty"` + Measurements string `json:"measurements,omitempty"` + FakeTits string `json:"fake_tits,omitempty"` + CareerLength string `json:"career_length,omitempty"` + Tattoos string `json:"tattoos,omitempty"` + Piercings string `json:"piercings,omitempty"` + Aliases string `json:"aliases,omitempty"` + Favorite bool `json:"favorite,omitempty"` + Tags []string `json:"tags,omitempty"` + Image string `json:"image,omitempty"` + CreatedAt models.JSONTime `json:"created_at,omitempty"` + UpdatedAt models.JSONTime `json:"updated_at,omitempty"` + Rating int `json:"rating,omitempty"` + Details string `json:"details,omitempty"` + DeathDate string `json:"death_date,omitempty"` + HairColor string `json:"hair_color,omitempty"` + Weight int `json:"weight,omitempty"` + StashIDs []models.StashID `json:"stash_ids,omitempty"` + IgnoreAutoTag bool `json:"ignore_auto_tag,omitempty"` } func LoadPerformerFile(filePath string) (*Performer, error) { diff --git a/pkg/models/jsonschema/studio.go b/pkg/models/jsonschema/studio.go index 5ecf04335..142ace053 100644 --- a/pkg/models/jsonschema/studio.go +++ b/pkg/models/jsonschema/studio.go @@ -9,16 +9,17 @@ import ( ) type Studio struct { - Name string `json:"name,omitempty"` - URL string `json:"url,omitempty"` - ParentStudio string `json:"parent_studio,omitempty"` - Image string `json:"image,omitempty"` - CreatedAt models.JSONTime `json:"created_at,omitempty"` - UpdatedAt models.JSONTime `json:"updated_at,omitempty"` - Rating int `json:"rating,omitempty"` - Details string `json:"details,omitempty"` - Aliases []string `json:"aliases,omitempty"` - StashIDs []models.StashID `json:"stash_ids,omitempty"` + Name string `json:"name,omitempty"` + URL string `json:"url,omitempty"` + ParentStudio string `json:"parent_studio,omitempty"` + Image string `json:"image,omitempty"` + CreatedAt models.JSONTime `json:"created_at,omitempty"` + UpdatedAt models.JSONTime `json:"updated_at,omitempty"` + Rating int `json:"rating,omitempty"` + Details string `json:"details,omitempty"` + Aliases []string `json:"aliases,omitempty"` + StashIDs []models.StashID `json:"stash_ids,omitempty"` + IgnoreAutoTag bool `json:"ignore_auto_tag,omitempty"` } func LoadStudioFile(filePath string) (*Studio, error) { diff --git a/pkg/models/jsonschema/tag.go b/pkg/models/jsonschema/tag.go index fbb85a835..9b247e763 100644 --- a/pkg/models/jsonschema/tag.go +++ b/pkg/models/jsonschema/tag.go @@ -9,12 +9,13 @@ import ( ) type Tag struct { - Name string `json:"name,omitempty"` - Aliases []string `json:"aliases,omitempty"` - Image string `json:"image,omitempty"` - Parents []string `json:"parents,omitempty"` - CreatedAt models.JSONTime `json:"created_at,omitempty"` - UpdatedAt models.JSONTime `json:"updated_at,omitempty"` + Name string `json:"name,omitempty"` + Aliases []string `json:"aliases,omitempty"` + Image string `json:"image,omitempty"` + Parents []string `json:"parents,omitempty"` + IgnoreAutoTag bool `json:"ignore_auto_tag,omitempty"` + CreatedAt models.JSONTime `json:"created_at,omitempty"` + UpdatedAt models.JSONTime `json:"updated_at,omitempty"` } func LoadTagFile(filePath string) (*Tag, error) { diff --git a/pkg/models/model_performer.go b/pkg/models/model_performer.go index 651e532d9..0f10344f7 100644 --- a/pkg/models/model_performer.go +++ b/pkg/models/model_performer.go @@ -8,61 +8,63 @@ import ( ) type Performer struct { - ID int `db:"id" json:"id"` - Checksum string `db:"checksum" json:"checksum"` - Name sql.NullString `db:"name" json:"name"` - Gender sql.NullString `db:"gender" json:"gender"` - URL sql.NullString `db:"url" json:"url"` - Twitter sql.NullString `db:"twitter" json:"twitter"` - Instagram sql.NullString `db:"instagram" json:"instagram"` - Birthdate SQLiteDate `db:"birthdate" json:"birthdate"` - Ethnicity sql.NullString `db:"ethnicity" json:"ethnicity"` - Country sql.NullString `db:"country" json:"country"` - EyeColor sql.NullString `db:"eye_color" json:"eye_color"` - Height sql.NullString `db:"height" json:"height"` - Measurements sql.NullString `db:"measurements" json:"measurements"` - FakeTits sql.NullString `db:"fake_tits" json:"fake_tits"` - CareerLength sql.NullString `db:"career_length" json:"career_length"` - Tattoos sql.NullString `db:"tattoos" json:"tattoos"` - Piercings sql.NullString `db:"piercings" json:"piercings"` - Aliases sql.NullString `db:"aliases" json:"aliases"` - Favorite sql.NullBool `db:"favorite" json:"favorite"` - CreatedAt SQLiteTimestamp `db:"created_at" json:"created_at"` - UpdatedAt SQLiteTimestamp `db:"updated_at" json:"updated_at"` - Rating sql.NullInt64 `db:"rating" json:"rating"` - Details sql.NullString `db:"details" json:"details"` - DeathDate SQLiteDate `db:"death_date" json:"death_date"` - HairColor sql.NullString `db:"hair_color" json:"hair_color"` - Weight sql.NullInt64 `db:"weight" json:"weight"` + ID int `db:"id" json:"id"` + Checksum string `db:"checksum" json:"checksum"` + Name sql.NullString `db:"name" json:"name"` + Gender sql.NullString `db:"gender" json:"gender"` + URL sql.NullString `db:"url" json:"url"` + Twitter sql.NullString `db:"twitter" json:"twitter"` + Instagram sql.NullString `db:"instagram" json:"instagram"` + Birthdate SQLiteDate `db:"birthdate" json:"birthdate"` + Ethnicity sql.NullString `db:"ethnicity" json:"ethnicity"` + Country sql.NullString `db:"country" json:"country"` + EyeColor sql.NullString `db:"eye_color" json:"eye_color"` + Height sql.NullString `db:"height" json:"height"` + Measurements sql.NullString `db:"measurements" json:"measurements"` + FakeTits sql.NullString `db:"fake_tits" json:"fake_tits"` + CareerLength sql.NullString `db:"career_length" json:"career_length"` + Tattoos sql.NullString `db:"tattoos" json:"tattoos"` + Piercings sql.NullString `db:"piercings" json:"piercings"` + Aliases sql.NullString `db:"aliases" json:"aliases"` + Favorite sql.NullBool `db:"favorite" json:"favorite"` + CreatedAt SQLiteTimestamp `db:"created_at" json:"created_at"` + UpdatedAt SQLiteTimestamp `db:"updated_at" json:"updated_at"` + Rating sql.NullInt64 `db:"rating" json:"rating"` + Details sql.NullString `db:"details" json:"details"` + DeathDate SQLiteDate `db:"death_date" json:"death_date"` + HairColor sql.NullString `db:"hair_color" json:"hair_color"` + Weight sql.NullInt64 `db:"weight" json:"weight"` + IgnoreAutoTag bool `db:"ignore_auto_tag" json:"ignore_auto_tag"` } type PerformerPartial struct { - ID int `db:"id" json:"id"` - Checksum *string `db:"checksum" json:"checksum"` - Name *sql.NullString `db:"name" json:"name"` - Gender *sql.NullString `db:"gender" json:"gender"` - URL *sql.NullString `db:"url" json:"url"` - Twitter *sql.NullString `db:"twitter" json:"twitter"` - Instagram *sql.NullString `db:"instagram" json:"instagram"` - Birthdate *SQLiteDate `db:"birthdate" json:"birthdate"` - Ethnicity *sql.NullString `db:"ethnicity" json:"ethnicity"` - Country *sql.NullString `db:"country" json:"country"` - EyeColor *sql.NullString `db:"eye_color" json:"eye_color"` - Height *sql.NullString `db:"height" json:"height"` - Measurements *sql.NullString `db:"measurements" json:"measurements"` - FakeTits *sql.NullString `db:"fake_tits" json:"fake_tits"` - CareerLength *sql.NullString `db:"career_length" json:"career_length"` - Tattoos *sql.NullString `db:"tattoos" json:"tattoos"` - Piercings *sql.NullString `db:"piercings" json:"piercings"` - Aliases *sql.NullString `db:"aliases" json:"aliases"` - Favorite *sql.NullBool `db:"favorite" json:"favorite"` - CreatedAt *SQLiteTimestamp `db:"created_at" json:"created_at"` - UpdatedAt *SQLiteTimestamp `db:"updated_at" json:"updated_at"` - Rating *sql.NullInt64 `db:"rating" json:"rating"` - Details *sql.NullString `db:"details" json:"details"` - DeathDate *SQLiteDate `db:"death_date" json:"death_date"` - HairColor *sql.NullString `db:"hair_color" json:"hair_color"` - Weight *sql.NullInt64 `db:"weight" json:"weight"` + ID int `db:"id" json:"id"` + Checksum *string `db:"checksum" json:"checksum"` + Name *sql.NullString `db:"name" json:"name"` + Gender *sql.NullString `db:"gender" json:"gender"` + URL *sql.NullString `db:"url" json:"url"` + Twitter *sql.NullString `db:"twitter" json:"twitter"` + Instagram *sql.NullString `db:"instagram" json:"instagram"` + Birthdate *SQLiteDate `db:"birthdate" json:"birthdate"` + Ethnicity *sql.NullString `db:"ethnicity" json:"ethnicity"` + Country *sql.NullString `db:"country" json:"country"` + EyeColor *sql.NullString `db:"eye_color" json:"eye_color"` + Height *sql.NullString `db:"height" json:"height"` + Measurements *sql.NullString `db:"measurements" json:"measurements"` + FakeTits *sql.NullString `db:"fake_tits" json:"fake_tits"` + CareerLength *sql.NullString `db:"career_length" json:"career_length"` + Tattoos *sql.NullString `db:"tattoos" json:"tattoos"` + Piercings *sql.NullString `db:"piercings" json:"piercings"` + Aliases *sql.NullString `db:"aliases" json:"aliases"` + Favorite *sql.NullBool `db:"favorite" json:"favorite"` + CreatedAt *SQLiteTimestamp `db:"created_at" json:"created_at"` + UpdatedAt *SQLiteTimestamp `db:"updated_at" json:"updated_at"` + Rating *sql.NullInt64 `db:"rating" json:"rating"` + Details *sql.NullString `db:"details" json:"details"` + DeathDate *SQLiteDate `db:"death_date" json:"death_date"` + HairColor *sql.NullString `db:"hair_color" json:"hair_color"` + Weight *sql.NullInt64 `db:"weight" json:"weight"` + IgnoreAutoTag *bool `db:"ignore_auto_tag" json:"ignore_auto_tag"` } func NewPerformer(name string) *Performer { diff --git a/pkg/models/model_studio.go b/pkg/models/model_studio.go index 34f9504d3..55b0e03aa 100644 --- a/pkg/models/model_studio.go +++ b/pkg/models/model_studio.go @@ -8,27 +8,29 @@ import ( ) type Studio struct { - ID int `db:"id" json:"id"` - Checksum string `db:"checksum" json:"checksum"` - Name sql.NullString `db:"name" json:"name"` - URL sql.NullString `db:"url" json:"url"` - ParentID sql.NullInt64 `db:"parent_id,omitempty" json:"parent_id"` - CreatedAt SQLiteTimestamp `db:"created_at" json:"created_at"` - UpdatedAt SQLiteTimestamp `db:"updated_at" json:"updated_at"` - Rating sql.NullInt64 `db:"rating" json:"rating"` - Details sql.NullString `db:"details" json:"details"` + ID int `db:"id" json:"id"` + Checksum string `db:"checksum" json:"checksum"` + Name sql.NullString `db:"name" json:"name"` + URL sql.NullString `db:"url" json:"url"` + ParentID sql.NullInt64 `db:"parent_id,omitempty" json:"parent_id"` + CreatedAt SQLiteTimestamp `db:"created_at" json:"created_at"` + UpdatedAt SQLiteTimestamp `db:"updated_at" json:"updated_at"` + Rating sql.NullInt64 `db:"rating" json:"rating"` + Details sql.NullString `db:"details" json:"details"` + IgnoreAutoTag bool `db:"ignore_auto_tag" json:"ignore_auto_tag"` } type StudioPartial struct { - ID int `db:"id" json:"id"` - Checksum *string `db:"checksum" json:"checksum"` - Name *sql.NullString `db:"name" json:"name"` - URL *sql.NullString `db:"url" json:"url"` - ParentID *sql.NullInt64 `db:"parent_id,omitempty" json:"parent_id"` - CreatedAt *SQLiteTimestamp `db:"created_at" json:"created_at"` - UpdatedAt *SQLiteTimestamp `db:"updated_at" json:"updated_at"` - Rating *sql.NullInt64 `db:"rating" json:"rating"` - Details *sql.NullString `db:"details" json:"details"` + ID int `db:"id" json:"id"` + Checksum *string `db:"checksum" json:"checksum"` + Name *sql.NullString `db:"name" json:"name"` + URL *sql.NullString `db:"url" json:"url"` + ParentID *sql.NullInt64 `db:"parent_id,omitempty" json:"parent_id"` + CreatedAt *SQLiteTimestamp `db:"created_at" json:"created_at"` + UpdatedAt *SQLiteTimestamp `db:"updated_at" json:"updated_at"` + Rating *sql.NullInt64 `db:"rating" json:"rating"` + Details *sql.NullString `db:"details" json:"details"` + IgnoreAutoTag *bool `db:"ignore_auto_tag" json:"ignore_auto_tag"` } var DefaultStudioImage = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4wgVBQsJl1CMZAAAASJJREFUeNrt3N0JwyAYhlEj3cj9R3Cm5rbkqtAP+qrnGaCYHPwJpLlaa++mmLpbAERAgAgIEAEBIiBABERAgAgIEAEBIiBABERAgAgIEAHZuVflj40x4i94zhk9vqsVvEq6AsQqMP1EjORx20OACAgQRRx7T+zzcFBxcjNDfoB4ntQqTm5Awo7MlqywZxcgYQ+RlqywJ3ozJAQCSBiEJSsQA0gYBpDAgAARECACAkRAgAgIEAERECACAmSjUv6eAOSB8m8YIGGzBUjYbAESBgMkbBkDEjZbgITBAClcxiqQvEoatreYIWEBASIgJ4Gkf11ntXH3nS9uxfGWfJ5J9hAgAgJEQAQEiIAAERAgAgJEQAQEiIAAERAgAgJEQAQEiL7qBuc6RKLHxr0CAAAAAElFTkSuQmCC" diff --git a/pkg/models/model_tag.go b/pkg/models/model_tag.go index 90fc67bd9..b7ec20c3f 100644 --- a/pkg/models/model_tag.go +++ b/pkg/models/model_tag.go @@ -3,17 +3,19 @@ package models import "time" type Tag struct { - ID int `db:"id" json:"id"` - Name string `db:"name" json:"name"` // TODO make schema not null - CreatedAt SQLiteTimestamp `db:"created_at" json:"created_at"` - UpdatedAt SQLiteTimestamp `db:"updated_at" json:"updated_at"` + ID int `db:"id" json:"id"` + Name string `db:"name" json:"name"` // TODO make schema not null + IgnoreAutoTag bool `db:"ignore_auto_tag" json:"ignore_auto_tag"` + CreatedAt SQLiteTimestamp `db:"created_at" json:"created_at"` + UpdatedAt SQLiteTimestamp `db:"updated_at" json:"updated_at"` } type TagPartial struct { - ID int `db:"id" json:"id"` - Name *string `db:"name" json:"name"` // TODO make schema not null - CreatedAt *SQLiteTimestamp `db:"created_at" json:"created_at"` - UpdatedAt *SQLiteTimestamp `db:"updated_at" json:"updated_at"` + ID int `db:"id" json:"id"` + Name *string `db:"name" json:"name"` // TODO make schema not null + IgnoreAutoTag *bool `db:"ignore_auto_tag" json:"ignore_auto_tag"` + CreatedAt *SQLiteTimestamp `db:"created_at" json:"created_at"` + UpdatedAt *SQLiteTimestamp `db:"updated_at" json:"updated_at"` } type TagPath struct { diff --git a/pkg/performer/export.go b/pkg/performer/export.go index 2b7b45ab1..4c0ce1560 100644 --- a/pkg/performer/export.go +++ b/pkg/performer/export.go @@ -11,8 +11,9 @@ import ( // ToJSON converts a Performer object into its JSON equivalent. func ToJSON(reader models.PerformerReader, performer *models.Performer) (*jsonschema.Performer, error) { newPerformerJSON := jsonschema.Performer{ - CreatedAt: models.JSONTime{Time: performer.CreatedAt.Timestamp}, - UpdatedAt: models.JSONTime{Time: performer.UpdatedAt.Timestamp}, + IgnoreAutoTag: performer.IgnoreAutoTag, + CreatedAt: models.JSONTime{Time: performer.CreatedAt.Timestamp}, + UpdatedAt: models.JSONTime{Time: performer.UpdatedAt.Timestamp}, } if performer.Name.Valid { diff --git a/pkg/performer/export_test.go b/pkg/performer/export_test.go index 83c9f0fb6..44d5b3ffe 100644 --- a/pkg/performer/export_test.go +++ b/pkg/performer/export_test.go @@ -21,25 +21,26 @@ const ( ) const ( - performerName = "testPerformer" - url = "url" - aliases = "aliases" - careerLength = "careerLength" - country = "country" - ethnicity = "ethnicity" - eyeColor = "eyeColor" - fakeTits = "fakeTits" - gender = "gender" - height = "height" - instagram = "instagram" - measurements = "measurements" - piercings = "piercings" - tattoos = "tattoos" - twitter = "twitter" - rating = 5 - details = "details" - hairColor = "hairColor" - weight = 60 + performerName = "testPerformer" + url = "url" + aliases = "aliases" + careerLength = "careerLength" + country = "country" + ethnicity = "ethnicity" + eyeColor = "eyeColor" + fakeTits = "fakeTits" + gender = "gender" + height = "height" + instagram = "instagram" + measurements = "measurements" + piercings = "piercings" + tattoos = "tattoos" + twitter = "twitter" + rating = 5 + details = "details" + hairColor = "hairColor" + weight = 60 + autoTagIgnored = true ) var imageBytes = []byte("imageBytes") @@ -106,6 +107,7 @@ func createFullPerformer(id int, name string) *models.Performer { Int64: weight, Valid: true, }, + IgnoreAutoTag: autoTagIgnored, } } @@ -155,6 +157,7 @@ func createFullJSONPerformer(name string, image string) *jsonschema.Performer { StashIDs: []models.StashID{ stashID, }, + IgnoreAutoTag: autoTagIgnored, } } diff --git a/pkg/performer/import.go b/pkg/performer/import.go index 071a62dda..9e4ec77f7 100644 --- a/pkg/performer/import.go +++ b/pkg/performer/import.go @@ -178,10 +178,11 @@ func performerJSONToPerformer(performerJSON jsonschema.Performer) models.Perform checksum := md5.FromString(performerJSON.Name) newPerformer := models.Performer{ - Checksum: checksum, - Favorite: sql.NullBool{Bool: performerJSON.Favorite, Valid: true}, - CreatedAt: models.SQLiteTimestamp{Timestamp: performerJSON.CreatedAt.GetTime()}, - UpdatedAt: models.SQLiteTimestamp{Timestamp: performerJSON.UpdatedAt.GetTime()}, + Checksum: checksum, + Favorite: sql.NullBool{Bool: performerJSON.Favorite, Valid: true}, + IgnoreAutoTag: performerJSON.IgnoreAutoTag, + CreatedAt: models.SQLiteTimestamp{Timestamp: performerJSON.CreatedAt.GetTime()}, + UpdatedAt: models.SQLiteTimestamp{Timestamp: performerJSON.UpdatedAt.GetTime()}, } if performerJSON.Name != "" { diff --git a/pkg/sqlite/performer.go b/pkg/sqlite/performer.go index 2994c5dba..142be42ff 100644 --- a/pkg/sqlite/performer.go +++ b/pkg/sqlite/performer.go @@ -191,7 +191,11 @@ func (qb *performerQueryBuilder) QueryForAutoTag(words []string) ([]*models.Perf // args = append(args, w+"%") } - where := strings.Join(whereClauses, " OR ") + whereOr := "(" + strings.Join(whereClauses, " OR ") + ")" + where := strings.Join([]string{ + "ignore_auto_tag = 0", + whereOr, + }, " AND ") return qb.queryPerformers(query+" WHERE "+where, args) } @@ -244,6 +248,7 @@ func (qb *performerQueryBuilder) makeFilter(filter *models.PerformerFilterType) query.handleCriterion(stringCriterionHandler(filter.Details, tableName+".details")) query.handleCriterion(boolCriterionHandler(filter.FilterFavorites, tableName+".favorite")) + query.handleCriterion(boolCriterionHandler(filter.IgnoreAutoTag, tableName+".ignore_auto_tag")) query.handleCriterion(yearFilterCriterionHandler(filter.BirthYear, tableName+".birthdate")) query.handleCriterion(yearFilterCriterionHandler(filter.DeathYear, tableName+".death_date")) diff --git a/pkg/sqlite/performer_test.go b/pkg/sqlite/performer_test.go index e8ca56db5..a6839f573 100644 --- a/pkg/sqlite/performer_test.go +++ b/pkg/sqlite/performer_test.go @@ -6,6 +6,7 @@ package sqlite_test import ( "database/sql" "fmt" + "math" "strconv" "strings" "testing" @@ -238,11 +239,31 @@ func TestPerformerIllegalQuery(t *testing.T) { }) } +func TestPerformerQueryIgnoreAutoTag(t *testing.T) { + withTxn(func(r models.Repository) error { + ignoreAutoTag := true + performerFilter := models.PerformerFilterType{ + IgnoreAutoTag: &ignoreAutoTag, + } + + sqb := r.Performer() + + performers := queryPerformers(t, sqb, &performerFilter, nil) + + assert.Len(t, performers, int(math.Ceil(float64(totalPerformers)/5))) + for _, p := range performers { + assert.True(t, p.IgnoreAutoTag) + } + + return nil + }) +} + func TestPerformerQueryForAutoTag(t *testing.T) { withTxn(func(r models.Repository) error { tqb := r.Performer() - name := performerNames[performerIdxWithScene] // find a performer by name + name := performerNames[performerIdx1WithScene] // find a performer by name performers, err := tqb.QueryForAutoTag([]string{name}) @@ -251,8 +272,8 @@ func TestPerformerQueryForAutoTag(t *testing.T) { } assert.Len(t, performers, 2) - assert.Equal(t, strings.ToLower(performerNames[performerIdxWithScene]), strings.ToLower(performers[0].Name.String)) - assert.Equal(t, strings.ToLower(performerNames[performerIdxWithScene]), strings.ToLower(performers[1].Name.String)) + assert.Equal(t, strings.ToLower(performerNames[performerIdx1WithScene]), strings.ToLower(performers[0].Name.String)) + assert.Equal(t, strings.ToLower(performerNames[performerIdx1WithScene]), strings.ToLower(performers[1].Name.String)) return nil }) diff --git a/pkg/sqlite/setup_test.go b/pkg/sqlite/setup_test.go index 4e4189b6c..e07d8aebe 100644 --- a/pkg/sqlite/setup_test.go +++ b/pkg/sqlite/setup_test.go @@ -99,6 +99,8 @@ const ( performersNameCase = performerIdx1WithDupName performersNameNoCase = 2 + + totalPerformers = performersNameCase + performersNameNoCase ) const ( @@ -166,6 +168,8 @@ const ( tagsNameNoCase = 2 tagsNameCase = tagIdx1WithDupName + + totalTags = tagsNameCase + tagsNameNoCase ) const ( @@ -190,6 +194,8 @@ const ( studiosNameCase = studioIdxWithDupName studiosNameNoCase = 1 + + totalStudios = studiosNameCase + studiosNameNoCase ) const ( @@ -422,7 +428,9 @@ func runTests(m *testing.M) int { f.Close() databaseFile := f.Name() - database.Initialize(databaseFile) + if err := database.Initialize(databaseFile); err != nil { + panic(fmt.Sprintf("Could not initialize database: %s", err.Error())) + } // defer close and delete the database defer testTeardown(databaseFile) @@ -815,6 +823,10 @@ func getPerformerCareerLength(index int) *string { return &ret } +func getIgnoreAutoTag(index int) bool { + return index%5 == 0 +} + // createPerformers creates n performers with plain Name and o performers with camel cased NaMe included func createPerformers(pqb models.PerformerReaderWriter, n int, o int) error { const namePlain = "Name" @@ -840,10 +852,11 @@ func createPerformers(pqb models.PerformerReaderWriter, n int, o int) error { String: getPerformerBirthdate(i), Valid: true, }, - DeathDate: getPerformerDeathDate(i), - Details: sql.NullString{String: getPerformerStringValue(i, "Details"), Valid: true}, - Ethnicity: sql.NullString{String: getPerformerStringValue(i, "Ethnicity"), Valid: true}, - Rating: getRating(i), + DeathDate: getPerformerDeathDate(i), + Details: sql.NullString{String: getPerformerStringValue(i, "Details"), Valid: true}, + Ethnicity: sql.NullString{String: getPerformerStringValue(i, "Ethnicity"), Valid: true}, + Rating: getRating(i), + IgnoreAutoTag: getIgnoreAutoTag(i), } careerLength := getPerformerCareerLength(i) @@ -945,7 +958,8 @@ func createTags(tqb models.TagReaderWriter, n int, o int) error { // tags [ i ] and [ n + o - i - 1 ] should have similar names with only the Name!=NaMe part different tag := models.Tag{ - Name: getTagStringValue(index, name), + Name: getTagStringValue(index, name), + IgnoreAutoTag: getIgnoreAutoTag(i), } created, err := tqb.Create(tag) @@ -1015,9 +1029,10 @@ func createStudios(sqb models.StudioReaderWriter, n int, o int) error { name = getStudioStringValue(index, name) studio := models.Studio{ - Name: sql.NullString{String: name, Valid: true}, - Checksum: md5.FromString(name), - URL: getStudioNullStringValue(index, urlField), + Name: sql.NullString{String: name, Valid: true}, + Checksum: md5.FromString(name), + URL: getStudioNullStringValue(index, urlField), + IgnoreAutoTag: getIgnoreAutoTag(i), } created, err := createStudioFromModel(sqb, studio) diff --git a/pkg/sqlite/studio.go b/pkg/sqlite/studio.go index 6b58ab0f8..cc810fe0c 100644 --- a/pkg/sqlite/studio.go +++ b/pkg/sqlite/studio.go @@ -154,7 +154,11 @@ func (qb *studioQueryBuilder) QueryForAutoTag(words []string) ([]*models.Studio, args = append(args, ww) } - where := strings.Join(whereClauses, " OR ") + whereOr := "(" + strings.Join(whereClauses, " OR ") + ")" + where := strings.Join([]string{ + "studios.ignore_auto_tag = 0", + whereOr, + }, " AND ") return qb.queryStudios(query+" WHERE "+where, args) } @@ -206,6 +210,7 @@ func (qb *studioQueryBuilder) makeFilter(studioFilter *models.StudioFilterType) query.handleCriterion(stringCriterionHandler(studioFilter.Details, studioTable+".details")) query.handleCriterion(stringCriterionHandler(studioFilter.URL, studioTable+".url")) query.handleCriterion(intCriterionHandler(studioFilter.Rating, studioTable+".rating")) + query.handleCriterion(boolCriterionHandler(studioFilter.IgnoreAutoTag, studioTable+".ignore_auto_tag")) query.handleCriterion(criterionHandlerFunc(func(f *filterBuilder) { if studioFilter.StashID != nil { diff --git a/pkg/sqlite/studio_test.go b/pkg/sqlite/studio_test.go index 037c5958a..08e6a30da 100644 --- a/pkg/sqlite/studio_test.go +++ b/pkg/sqlite/studio_test.go @@ -7,6 +7,7 @@ import ( "database/sql" "errors" "fmt" + "math" "strconv" "strings" "testing" @@ -183,11 +184,31 @@ func TestStudioIllegalQuery(t *testing.T) { }) } +func TestStudioQueryIgnoreAutoTag(t *testing.T) { + withTxn(func(r models.Repository) error { + ignoreAutoTag := true + studioFilter := models.StudioFilterType{ + IgnoreAutoTag: &ignoreAutoTag, + } + + sqb := r.Studio() + + studios := queryStudio(t, sqb, &studioFilter, nil) + + assert.Len(t, studios, int(math.Ceil(float64(totalStudios)/5))) + for _, s := range studios { + assert.True(t, s.IgnoreAutoTag) + } + + return nil + }) +} + func TestStudioQueryForAutoTag(t *testing.T) { withTxn(func(r models.Repository) error { tqb := r.Studio() - name := studioNames[studioIdxWithScene] // find a studio by name + name := studioNames[studioIdxWithMovie] // find a studio by name studios, err := tqb.QueryForAutoTag([]string{name}) @@ -195,12 +216,11 @@ func TestStudioQueryForAutoTag(t *testing.T) { t.Errorf("Error finding studios: %s", err.Error()) } - assert.Len(t, studios, 2) - assert.Equal(t, strings.ToLower(studioNames[studioIdxWithScene]), strings.ToLower(studios[0].Name.String)) - assert.Equal(t, strings.ToLower(studioNames[studioIdxWithScene]), strings.ToLower(studios[1].Name.String)) + assert.Len(t, studios, 1) + assert.Equal(t, strings.ToLower(studioNames[studioIdxWithMovie]), strings.ToLower(studios[0].Name.String)) // find by alias - name = getStudioStringValue(studioIdxWithScene, "Alias") + name = getStudioStringValue(studioIdxWithMovie, "Alias") studios, err = tqb.QueryForAutoTag([]string{name}) if err != nil { @@ -208,7 +228,7 @@ func TestStudioQueryForAutoTag(t *testing.T) { } assert.Len(t, studios, 1) - assert.Equal(t, studioIDs[studioIdxWithScene], studios[0].ID) + assert.Equal(t, studioIDs[studioIdxWithMovie], studios[0].ID) return nil }) diff --git a/pkg/sqlite/tag.go b/pkg/sqlite/tag.go index 2187ff08e..9513a269b 100644 --- a/pkg/sqlite/tag.go +++ b/pkg/sqlite/tag.go @@ -245,7 +245,11 @@ func (qb *tagQueryBuilder) QueryForAutoTag(words []string) ([]*models.Tag, error args = append(args, ww) } - where := strings.Join(whereClauses, " OR ") + whereOr := "(" + strings.Join(whereClauses, " OR ") + ")" + where := strings.Join([]string{ + "tags.ignore_auto_tag = 0", + whereOr, + }, " AND ") return qb.queryTags(query+" WHERE "+where, args) } @@ -295,6 +299,7 @@ func (qb *tagQueryBuilder) makeFilter(tagFilter *models.TagFilterType) *filterBu query.handleCriterion(stringCriterionHandler(tagFilter.Name, tagTable+".name")) query.handleCriterion(tagAliasCriterionHandler(qb, tagFilter.Aliases)) + query.handleCriterion(boolCriterionHandler(tagFilter.IgnoreAutoTag, tagTable+".ignore_auto_tag")) query.handleCriterion(tagIsMissingCriterionHandler(qb, tagFilter.IsMissing)) query.handleCriterion(tagSceneCountCriterionHandler(qb, tagFilter.SceneCount)) diff --git a/pkg/sqlite/tag_test.go b/pkg/sqlite/tag_test.go index 70284019f..a5ed8d966 100644 --- a/pkg/sqlite/tag_test.go +++ b/pkg/sqlite/tag_test.go @@ -6,6 +6,7 @@ package sqlite_test import ( "database/sql" "fmt" + "math" "strconv" "strings" "testing" @@ -72,11 +73,31 @@ func TestTagFindByName(t *testing.T) { }) } +func TestTagQueryIgnoreAutoTag(t *testing.T) { + withTxn(func(r models.Repository) error { + ignoreAutoTag := true + tagFilter := models.TagFilterType{ + IgnoreAutoTag: &ignoreAutoTag, + } + + sqb := r.Tag() + + tags := queryTags(t, sqb, &tagFilter, nil) + + assert.Len(t, tags, int(math.Ceil(float64(totalTags)/5))) + for _, s := range tags { + assert.True(t, s.IgnoreAutoTag) + } + + return nil + }) +} + func TestTagQueryForAutoTag(t *testing.T) { withTxn(func(r models.Repository) error { tqb := r.Tag() - name := tagNames[tagIdxWithScene] // find a tag by name + name := tagNames[tagIdx1WithScene] // find a tag by name tags, err := tqb.QueryForAutoTag([]string{name}) @@ -85,12 +106,12 @@ func TestTagQueryForAutoTag(t *testing.T) { } assert.Len(t, tags, 2) - lcName := tagNames[tagIdxWithScene] + lcName := tagNames[tagIdx1WithScene] assert.Equal(t, strings.ToLower(lcName), strings.ToLower(tags[0].Name)) assert.Equal(t, strings.ToLower(lcName), strings.ToLower(tags[1].Name)) // find by alias - name = getTagStringValue(tagIdxWithScene, "Alias") + name = getTagStringValue(tagIdx1WithScene, "Alias") tags, err = tqb.QueryForAutoTag([]string{name}) if err != nil { @@ -98,7 +119,7 @@ func TestTagQueryForAutoTag(t *testing.T) { } assert.Len(t, tags, 1) - assert.Equal(t, tagIDs[tagIdxWithScene], tags[0].ID) + assert.Equal(t, tagIDs[tagIdx1WithScene], tags[0].ID) return nil }) diff --git a/pkg/studio/export.go b/pkg/studio/export.go index 30aa46482..82ebc9f40 100644 --- a/pkg/studio/export.go +++ b/pkg/studio/export.go @@ -11,8 +11,9 @@ import ( // ToJSON converts a Studio object into its JSON equivalent. func ToJSON(reader models.StudioReader, studio *models.Studio) (*jsonschema.Studio, error) { newStudioJSON := jsonschema.Studio{ - CreatedAt: models.JSONTime{Time: studio.CreatedAt.Timestamp}, - UpdatedAt: models.JSONTime{Time: studio.UpdatedAt.Timestamp}, + IgnoreAutoTag: studio.IgnoreAutoTag, + CreatedAt: models.JSONTime{Time: studio.CreatedAt.Timestamp}, + UpdatedAt: models.JSONTime{Time: studio.UpdatedAt.Timestamp}, } if studio.Name.Valid { diff --git a/pkg/studio/export_test.go b/pkg/studio/export_test.go index d1aae1948..f4e17e59e 100644 --- a/pkg/studio/export_test.go +++ b/pkg/studio/export_test.go @@ -31,6 +31,7 @@ const ( details = "details" rating = 5 parentStudioName = "parentStudio" + autoTagIgnored = true ) var parentStudio models.Studio = models.Studio{ @@ -66,7 +67,8 @@ func createFullStudio(id int, parentID int) models.Studio { UpdatedAt: models.SQLiteTimestamp{ Timestamp: updateTime, }, - Rating: models.NullInt64(rating), + Rating: models.NullInt64(rating), + IgnoreAutoTag: autoTagIgnored, } if parentID != 0 { @@ -106,6 +108,7 @@ func createFullJSONStudio(parentStudio, image string, aliases []string) *jsonsch StashIDs: []models.StashID{ stashID, }, + IgnoreAutoTag: autoTagIgnored, } } diff --git a/pkg/studio/import.go b/pkg/studio/import.go index 3bd21dded..a44481982 100644 --- a/pkg/studio/import.go +++ b/pkg/studio/import.go @@ -26,13 +26,14 @@ func (i *Importer) PreImport() error { checksum := md5.FromString(i.Input.Name) i.studio = models.Studio{ - Checksum: checksum, - Name: sql.NullString{String: i.Input.Name, Valid: true}, - URL: sql.NullString{String: i.Input.URL, Valid: true}, - Details: sql.NullString{String: i.Input.Details, Valid: true}, - CreatedAt: models.SQLiteTimestamp{Timestamp: i.Input.CreatedAt.GetTime()}, - UpdatedAt: models.SQLiteTimestamp{Timestamp: i.Input.UpdatedAt.GetTime()}, - Rating: sql.NullInt64{Int64: int64(i.Input.Rating), Valid: true}, + Checksum: checksum, + Name: sql.NullString{String: i.Input.Name, Valid: true}, + URL: sql.NullString{String: i.Input.URL, Valid: true}, + Details: sql.NullString{String: i.Input.Details, Valid: true}, + IgnoreAutoTag: i.Input.IgnoreAutoTag, + CreatedAt: models.SQLiteTimestamp{Timestamp: i.Input.CreatedAt.GetTime()}, + UpdatedAt: models.SQLiteTimestamp{Timestamp: i.Input.UpdatedAt.GetTime()}, + Rating: sql.NullInt64{Int64: int64(i.Input.Rating), Valid: true}, } if err := i.populateParentStudio(); err != nil { diff --git a/pkg/studio/import_test.go b/pkg/studio/import_test.go index 098cd2099..87b22519b 100644 --- a/pkg/studio/import_test.go +++ b/pkg/studio/import_test.go @@ -38,8 +38,9 @@ func TestImporterName(t *testing.T) { func TestImporterPreImport(t *testing.T) { i := Importer{ Input: jsonschema.Studio{ - Name: studioName, - Image: invalidImage, + Name: studioName, + Image: invalidImage, + IgnoreAutoTag: autoTagIgnored, }, } diff --git a/pkg/tag/export.go b/pkg/tag/export.go index 5a8c6fadc..bc9ba309f 100644 --- a/pkg/tag/export.go +++ b/pkg/tag/export.go @@ -11,9 +11,10 @@ import ( // ToJSON converts a Tag object into its JSON equivalent. func ToJSON(reader models.TagReader, tag *models.Tag) (*jsonschema.Tag, error) { newTagJSON := jsonschema.Tag{ - Name: tag.Name, - CreatedAt: models.JSONTime{Time: tag.CreatedAt.Timestamp}, - UpdatedAt: models.JSONTime{Time: tag.UpdatedAt.Timestamp}, + Name: tag.Name, + IgnoreAutoTag: tag.IgnoreAutoTag, + CreatedAt: models.JSONTime{Time: tag.CreatedAt.Timestamp}, + UpdatedAt: models.JSONTime{Time: tag.UpdatedAt.Timestamp}, } aliases, err := reader.GetAliases(tag.ID) diff --git a/pkg/tag/export_test.go b/pkg/tag/export_test.go index 5323c33de..b7d60fa9d 100644 --- a/pkg/tag/export_test.go +++ b/pkg/tag/export_test.go @@ -24,14 +24,16 @@ const ( const tagName = "testTag" var ( - createTime = time.Date(2001, 01, 01, 0, 0, 0, 0, time.UTC) - updateTime = time.Date(2002, 01, 01, 0, 0, 0, 0, time.UTC) + autoTagIgnored = true + createTime = time.Date(2001, 01, 01, 0, 0, 0, 0, time.UTC) + updateTime = time.Date(2002, 01, 01, 0, 0, 0, 0, time.UTC) ) func createTag(id int) models.Tag { return models.Tag{ - ID: id, - Name: tagName, + ID: id, + Name: tagName, + IgnoreAutoTag: autoTagIgnored, CreatedAt: models.SQLiteTimestamp{ Timestamp: createTime, }, @@ -43,8 +45,9 @@ func createTag(id int) models.Tag { func createJSONTag(aliases []string, image string, parents []string) *jsonschema.Tag { return &jsonschema.Tag{ - Name: tagName, - Aliases: aliases, + Name: tagName, + Aliases: aliases, + IgnoreAutoTag: autoTagIgnored, CreatedAt: models.JSONTime{ Time: createTime, }, diff --git a/pkg/tag/import.go b/pkg/tag/import.go index 66d608425..66028946c 100644 --- a/pkg/tag/import.go +++ b/pkg/tag/import.go @@ -31,9 +31,10 @@ type Importer struct { func (i *Importer) PreImport() error { i.tag = models.Tag{ - Name: i.Input.Name, - CreatedAt: models.SQLiteTimestamp{Timestamp: i.Input.CreatedAt.GetTime()}, - UpdatedAt: models.SQLiteTimestamp{Timestamp: i.Input.UpdatedAt.GetTime()}, + Name: i.Input.Name, + IgnoreAutoTag: i.Input.IgnoreAutoTag, + CreatedAt: models.SQLiteTimestamp{Timestamp: i.Input.CreatedAt.GetTime()}, + UpdatedAt: models.SQLiteTimestamp{Timestamp: i.Input.UpdatedAt.GetTime()}, } var err error diff --git a/pkg/tag/import_test.go b/pkg/tag/import_test.go index 296db7e97..fb6f3c58f 100644 --- a/pkg/tag/import_test.go +++ b/pkg/tag/import_test.go @@ -36,8 +36,9 @@ func TestImporterName(t *testing.T) { func TestImporterPreImport(t *testing.T) { i := Importer{ Input: jsonschema.Tag{ - Name: tagName, - Image: invalidImage, + Name: tagName, + Image: invalidImage, + IgnoreAutoTag: autoTagIgnored, }, } diff --git a/ui/v2.5/src/components/Changelog/versions/v0140.md b/ui/v2.5/src/components/Changelog/versions/v0140.md index 9081be4c5..1db2f3a5c 100644 --- a/ui/v2.5/src/components/Changelog/versions/v0140.md +++ b/ui/v2.5/src/components/Changelog/versions/v0140.md @@ -1,10 +1,11 @@ ##### 💥 Note: Image Slideshow Delay (in Interface Settings) is now in seconds rather than milliseconds and has not been converted. Please adjust your settings as needed. ### ✨ New Features +* Add Ignore Auto Tag flag to Performers, Studios and Tags. ([#2439](https://github.com/stashapp/stash/pull/2439)) * Add python location in System Settings for script scrapers and plugins. ([#2409](https://github.com/stashapp/stash/pull/2409)) ### 🎨 Improvements -* Revamped scene details page. ([#2466](https://github.com/stashapp/stash/pull/2466)) +* Restyled scene details page on mobile devices. ([#2466](https://github.com/stashapp/stash/pull/2466)) * Added support for Handy APIv2. ([#2193](https://github.com/stashapp/stash/pull/2193)) * Hide tabs with no content in Performer, Studio and Tag pages. ([#2468](https://github.com/stashapp/stash/pull/2468)) * Added support for bulk editing most performer fields. ([#2467](https://github.com/stashapp/stash/pull/2467)) diff --git a/ui/v2.5/src/components/Performers/EditPerformersDialog.tsx b/ui/v2.5/src/components/Performers/EditPerformersDialog.tsx index d06d0651a..3498af758 100644 --- a/ui/v2.5/src/components/Performers/EditPerformersDialog.tsx +++ b/ui/v2.5/src/components/Performers/EditPerformersDialog.tsx @@ -46,6 +46,7 @@ const performerFields = [ "hair_color", "tattoos", "piercings", + "ignore_auto_tag", ]; export const EditPerformersDialog: React.FC = ( @@ -302,6 +303,16 @@ export const EditPerformersDialog: React.FC = ( mode={tagIds.mode} /> + + + + setUpdateField({ ignore_auto_tag: checked }) + } + checked={updateInput.ignore_auto_tag ?? undefined} + /> + ); diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx index 5847715b2..348051d67 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx @@ -117,6 +117,7 @@ export const PerformerEditPanel: React.FC = ({ death_date: yup.string().optional(), hair_color: yup.string().optional(), weight: yup.number().optional(), + ignore_auto_tag: yup.boolean().optional(), }); const initialValues = { @@ -143,6 +144,7 @@ export const PerformerEditPanel: React.FC = ({ death_date: performer.death_date ?? "", hair_color: performer.hair_color ?? "", weight: performer.weight ?? undefined, + ignore_auto_tag: performer.ignore_auto_tag ?? false, }; type InputValues = typeof initialValues; @@ -944,6 +946,22 @@ export const PerformerEditPanel: React.FC = ({ {renderStashIDs()} +
+ + + + + + + + + + {renderButtons("mt-3")} diff --git a/ui/v2.5/src/components/Studios/StudioDetails/StudioEditPanel.tsx b/ui/v2.5/src/components/Studios/StudioDetails/StudioEditPanel.tsx index fa7af6832..f87aa7933 100644 --- a/ui/v2.5/src/components/Studios/StudioDetails/StudioEditPanel.tsx +++ b/ui/v2.5/src/components/Studios/StudioDetails/StudioEditPanel.tsx @@ -55,6 +55,7 @@ export const StudioEditPanel: React.FC = ({ }, message: "aliases must be unique", }), + ignore_auto_tag: yup.boolean().optional(), }); const initialValues = { @@ -66,6 +67,7 @@ export const StudioEditPanel: React.FC = ({ parent_id: studio.parent_studio?.id, stash_ids: studio.stash_ids ?? undefined, aliases: studio.aliases, + ignore_auto_tag: studio.ignore_auto_tag ?? false, }; type InputValues = typeof initialValues; @@ -317,6 +319,22 @@ export const StudioEditPanel: React.FC = ({ +
+ + + + + + + + + + = ({ }), parent_ids: yup.array(yup.string().required()).optional().nullable(), child_ids: yup.array(yup.string().required()).optional().nullable(), + ignore_auto_tag: yup.boolean().optional(), }); const initialValues = { @@ -60,6 +61,7 @@ export const TagEditPanel: React.FC = ({ aliases: tag?.aliases, parent_ids: (tag?.parents ?? []).map((t) => t.id), child_ids: (tag?.children ?? []).map((t) => t.id), + ignore_auto_tag: tag?.ignore_auto_tag ?? false, }; type InputValues = typeof initialValues; @@ -211,6 +213,22 @@ export const TagEditPanel: React.FC = ({ /> + +
+ + + + + + + + + createNumberCriterionOption(c)), ...stringCriteria.map((c) => createStringCriterionOption(c)), ]; diff --git a/ui/v2.5/src/models/list-filter/studios.ts b/ui/v2.5/src/models/list-filter/studios.ts index f24a3018c..f693572c1 100644 --- a/ui/v2.5/src/models/list-filter/studios.ts +++ b/ui/v2.5/src/models/list-filter/studios.ts @@ -1,4 +1,5 @@ import { + createBooleanCriterionOption, createMandatoryNumberCriterionOption, createMandatoryStringCriterionOption, createStringCriterionOption, @@ -34,6 +35,7 @@ const criterionOptions = [ ParentStudiosCriterionOption, StudioIsMissingCriterionOption, RatingCriterionOption, + createBooleanCriterionOption("ignore_auto_tag"), createMandatoryNumberCriterionOption("scene_count"), createMandatoryNumberCriterionOption("image_count"), createMandatoryNumberCriterionOption("gallery_count"), diff --git a/ui/v2.5/src/models/list-filter/tags.ts b/ui/v2.5/src/models/list-filter/tags.ts index bfa8104d9..7ab794509 100644 --- a/ui/v2.5/src/models/list-filter/tags.ts +++ b/ui/v2.5/src/models/list-filter/tags.ts @@ -1,4 +1,5 @@ import { + createBooleanCriterionOption, createMandatoryNumberCriterionOption, createMandatoryStringCriterionOption, createStringCriterionOption, @@ -43,6 +44,7 @@ const criterionOptions = [ createMandatoryStringCriterionOption("name"), TagIsMissingCriterionOption, createStringCriterionOption("aliases"), + createBooleanCriterionOption("ignore_auto_tag"), createMandatoryNumberCriterionOption("scene_count"), createMandatoryNumberCriterionOption("image_count"), createMandatoryNumberCriterionOption("gallery_count"), diff --git a/ui/v2.5/src/models/list-filter/types.ts b/ui/v2.5/src/models/list-filter/types.ts index 50009a76e..cd42c5403 100644 --- a/ui/v2.5/src/models/list-filter/types.ts +++ b/ui/v2.5/src/models/list-filter/types.ts @@ -127,4 +127,5 @@ export type CriterionType = | "child_tag_count" | "performer_favorite" | "performer_age" - | "duplicated"; + | "duplicated" + | "ignore_auto_tag";