2021-10-28 03:25:17 +00:00
|
|
|
package identify
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2023-07-11 04:37:00 +00:00
|
|
|
"errors"
|
2021-10-28 03:25:17 +00:00
|
|
|
"fmt"
|
2023-07-11 04:37:00 +00:00
|
|
|
"strconv"
|
2021-10-28 03:25:17 +00:00
|
|
|
|
|
|
|
"github.com/stashapp/stash/pkg/logger"
|
|
|
|
"github.com/stashapp/stash/pkg/models"
|
|
|
|
"github.com/stashapp/stash/pkg/scene"
|
2022-04-25 05:55:05 +00:00
|
|
|
"github.com/stashapp/stash/pkg/scraper"
|
2023-07-11 04:37:00 +00:00
|
|
|
"github.com/stashapp/stash/pkg/sliceutil/intslice"
|
2022-05-19 07:49:32 +00:00
|
|
|
"github.com/stashapp/stash/pkg/txn"
|
2021-10-28 03:25:17 +00:00
|
|
|
"github.com/stashapp/stash/pkg/utils"
|
|
|
|
)
|
|
|
|
|
2023-07-11 04:37:00 +00:00
|
|
|
var (
|
|
|
|
ErrSkipSingleNamePerformer = errors.New("a performer was skipped because they only had a single name and no disambiguation")
|
|
|
|
)
|
|
|
|
|
|
|
|
type MultipleMatchesFoundError struct {
|
|
|
|
Source ScraperSource
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *MultipleMatchesFoundError) Error() string {
|
|
|
|
return fmt.Sprintf("multiple matches found for %s", e.Source.Name)
|
|
|
|
}
|
|
|
|
|
2021-10-28 03:25:17 +00:00
|
|
|
type SceneScraper interface {
|
2023-07-11 04:37:00 +00:00
|
|
|
ScrapeScenes(ctx context.Context, sceneID int) ([]*scraper.ScrapedScene, error)
|
2021-10-28 03:25:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type SceneUpdatePostHookExecutor interface {
|
|
|
|
ExecuteSceneUpdatePostHooks(ctx context.Context, input models.SceneUpdateInput, inputFields []string)
|
|
|
|
}
|
|
|
|
|
|
|
|
type ScraperSource struct {
|
|
|
|
Name string
|
2022-04-25 05:55:05 +00:00
|
|
|
Options *MetadataOptions
|
2021-10-28 03:25:17 +00:00
|
|
|
Scraper SceneScraper
|
|
|
|
RemoteSite string
|
|
|
|
}
|
|
|
|
|
|
|
|
type SceneIdentifier struct {
|
2022-05-19 07:49:32 +00:00
|
|
|
SceneReaderUpdater SceneReaderUpdater
|
|
|
|
StudioCreator StudioCreator
|
|
|
|
PerformerCreator PerformerCreator
|
2023-07-11 04:37:00 +00:00
|
|
|
TagCreatorFinder TagCreatorFinder
|
2022-05-19 07:49:32 +00:00
|
|
|
|
2022-04-25 05:55:05 +00:00
|
|
|
DefaultOptions *MetadataOptions
|
2021-10-28 03:25:17 +00:00
|
|
|
Sources []ScraperSource
|
|
|
|
SceneUpdatePostHookExecutor SceneUpdatePostHookExecutor
|
|
|
|
}
|
|
|
|
|
2022-05-19 07:49:32 +00:00
|
|
|
func (t *SceneIdentifier) Identify(ctx context.Context, txnManager txn.Manager, scene *models.Scene) error {
|
2023-07-11 04:37:00 +00:00
|
|
|
result, err := t.scrapeScene(ctx, txnManager, scene)
|
|
|
|
var multipleMatchErr *MultipleMatchesFoundError
|
2021-10-28 03:25:17 +00:00
|
|
|
if err != nil {
|
2023-07-11 04:37:00 +00:00
|
|
|
if !errors.As(err, &multipleMatchErr) {
|
|
|
|
return err
|
|
|
|
}
|
2021-10-28 03:25:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if result == nil {
|
2023-07-11 04:37:00 +00:00
|
|
|
if multipleMatchErr != nil {
|
|
|
|
logger.Debugf("Identify skipped because multiple results returned for %s", scene.Path)
|
|
|
|
|
|
|
|
// find if the scene should be tagged for multiple results
|
|
|
|
options := t.getOptions(multipleMatchErr.Source)
|
|
|
|
if options.SkipMultipleMatchTag != nil && len(*options.SkipMultipleMatchTag) > 0 {
|
|
|
|
// Tag it with the multiple results tag
|
|
|
|
err := t.addTagToScene(ctx, txnManager, scene, *options.SkipMultipleMatchTag)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
logger.Debugf("Unable to identify %s", scene.Path)
|
|
|
|
}
|
2021-10-28 03:25:17 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// results were found, modify the scene
|
2021-11-05 22:58:52 +00:00
|
|
|
if err := t.modifyScene(ctx, txnManager, scene, result); err != nil {
|
2021-10-28 03:25:17 +00:00
|
|
|
return fmt.Errorf("error modifying scene: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type scrapeResult struct {
|
2022-04-25 05:55:05 +00:00
|
|
|
result *scraper.ScrapedScene
|
2021-10-28 03:25:17 +00:00
|
|
|
source ScraperSource
|
|
|
|
}
|
|
|
|
|
2023-07-11 04:37:00 +00:00
|
|
|
func (t *SceneIdentifier) scrapeScene(ctx context.Context, txnManager txn.Manager, scene *models.Scene) (*scrapeResult, error) {
|
2021-10-28 03:25:17 +00:00
|
|
|
// iterate through the input sources
|
|
|
|
for _, source := range t.Sources {
|
|
|
|
// scrape using the source
|
2023-07-11 04:37:00 +00:00
|
|
|
results, err := source.Scraper.ScrapeScenes(ctx, scene.ID)
|
2021-10-28 03:25:17 +00:00
|
|
|
if err != nil {
|
2022-03-14 22:42:22 +00:00
|
|
|
logger.Errorf("error scraping from %v: %v", source.Scraper, err)
|
|
|
|
continue
|
2021-10-28 03:25:17 +00:00
|
|
|
}
|
|
|
|
|
2023-07-11 04:37:00 +00:00
|
|
|
if len(results) > 0 {
|
|
|
|
options := t.getOptions(source)
|
|
|
|
if len(results) > 1 && utils.IsTrue(options.SkipMultipleMatches) {
|
|
|
|
return nil, &MultipleMatchesFoundError{
|
|
|
|
Source: source,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// if results were found then return
|
|
|
|
return &scrapeResult{
|
|
|
|
result: results[0],
|
|
|
|
source: source,
|
|
|
|
}, nil
|
|
|
|
}
|
2021-10-28 03:25:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2023-07-11 04:37:00 +00:00
|
|
|
// Returns a MetadataOptions object with any default options overwritten by source specific options
|
|
|
|
func (t *SceneIdentifier) getOptions(source ScraperSource) MetadataOptions {
|
|
|
|
options := *t.DefaultOptions
|
|
|
|
if source.Options == nil {
|
|
|
|
return options
|
|
|
|
}
|
|
|
|
if source.Options.SetCoverImage != nil {
|
|
|
|
options.SetCoverImage = source.Options.SetCoverImage
|
|
|
|
}
|
|
|
|
if source.Options.SetOrganized != nil {
|
|
|
|
options.SetOrganized = source.Options.SetOrganized
|
|
|
|
}
|
|
|
|
if source.Options.IncludeMalePerformers != nil {
|
|
|
|
options.IncludeMalePerformers = source.Options.IncludeMalePerformers
|
|
|
|
}
|
|
|
|
if source.Options.SkipMultipleMatches != nil {
|
|
|
|
options.SkipMultipleMatches = source.Options.SkipMultipleMatches
|
|
|
|
}
|
|
|
|
if source.Options.SkipMultipleMatchTag != nil && len(*source.Options.SkipMultipleMatchTag) > 0 {
|
|
|
|
options.SkipMultipleMatchTag = source.Options.SkipMultipleMatchTag
|
|
|
|
}
|
|
|
|
if source.Options.SkipSingleNamePerformers != nil {
|
|
|
|
options.SkipSingleNamePerformers = source.Options.SkipSingleNamePerformers
|
|
|
|
}
|
|
|
|
if source.Options.SkipSingleNamePerformerTag != nil && len(*source.Options.SkipSingleNamePerformerTag) > 0 {
|
|
|
|
options.SkipSingleNamePerformerTag = source.Options.SkipSingleNamePerformerTag
|
|
|
|
}
|
|
|
|
return options
|
|
|
|
}
|
|
|
|
|
2022-05-19 07:49:32 +00:00
|
|
|
func (t *SceneIdentifier) getSceneUpdater(ctx context.Context, s *models.Scene, result *scrapeResult) (*scene.UpdateSet, error) {
|
2021-10-28 03:25:17 +00:00
|
|
|
ret := &scene.UpdateSet{
|
|
|
|
ID: s.ID,
|
|
|
|
}
|
|
|
|
|
2023-07-11 04:37:00 +00:00
|
|
|
allOptions := []MetadataOptions{}
|
2021-10-28 03:25:17 +00:00
|
|
|
if result.source.Options != nil {
|
2023-07-11 04:37:00 +00:00
|
|
|
allOptions = append(allOptions, *result.source.Options)
|
2021-10-28 03:25:17 +00:00
|
|
|
}
|
|
|
|
if t.DefaultOptions != nil {
|
2023-07-11 04:37:00 +00:00
|
|
|
allOptions = append(allOptions, *t.DefaultOptions)
|
2021-10-28 03:25:17 +00:00
|
|
|
}
|
|
|
|
|
2023-07-11 04:37:00 +00:00
|
|
|
fieldOptions := getFieldOptions(allOptions)
|
|
|
|
options := t.getOptions(result.source)
|
2021-10-28 03:25:17 +00:00
|
|
|
|
|
|
|
scraped := result.result
|
|
|
|
|
|
|
|
rel := sceneRelationships{
|
2023-07-11 04:37:00 +00:00
|
|
|
sceneReader: t.SceneReaderUpdater,
|
|
|
|
studioCreator: t.StudioCreator,
|
|
|
|
performerCreator: t.PerformerCreator,
|
|
|
|
tagCreatorFinder: t.TagCreatorFinder,
|
|
|
|
scene: s,
|
|
|
|
result: result,
|
|
|
|
fieldOptions: fieldOptions,
|
2023-07-12 00:53:46 +00:00
|
|
|
skipSingleNamePerformers: utils.IsTrue(options.SkipSingleNamePerformers),
|
2021-10-28 03:25:17 +00:00
|
|
|
}
|
|
|
|
|
2023-07-12 00:53:46 +00:00
|
|
|
setOrganized := utils.IsTrue(options.SetOrganized)
|
2021-10-28 03:25:17 +00:00
|
|
|
ret.Partial = getScenePartial(s, scraped, fieldOptions, setOrganized)
|
|
|
|
|
2022-05-19 07:49:32 +00:00
|
|
|
studioID, err := rel.studio(ctx)
|
2021-10-28 03:25:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error getting studio: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if studioID != nil {
|
2022-07-13 06:30:54 +00:00
|
|
|
ret.Partial.StudioID = models.NewOptionalInt(*studioID)
|
2021-10-28 03:25:17 +00:00
|
|
|
}
|
|
|
|
|
2023-07-11 04:37:00 +00:00
|
|
|
includeMalePerformers := true
|
|
|
|
if options.IncludeMalePerformers != nil {
|
|
|
|
includeMalePerformers = *options.IncludeMalePerformers
|
2021-10-28 03:25:17 +00:00
|
|
|
}
|
|
|
|
|
2023-07-11 04:37:00 +00:00
|
|
|
addSkipSingleNamePerformerTag := false
|
|
|
|
performerIDs, err := rel.performers(ctx, !includeMalePerformers)
|
2021-10-28 03:25:17 +00:00
|
|
|
if err != nil {
|
2023-07-11 04:37:00 +00:00
|
|
|
if errors.Is(err, ErrSkipSingleNamePerformer) {
|
|
|
|
addSkipSingleNamePerformerTag = true
|
|
|
|
} else {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-10-28 03:25:17 +00:00
|
|
|
}
|
2022-07-13 06:30:54 +00:00
|
|
|
if performerIDs != nil {
|
|
|
|
ret.Partial.PerformerIDs = &models.UpdateIDs{
|
|
|
|
IDs: performerIDs,
|
|
|
|
Mode: models.RelationshipUpdateModeSet,
|
|
|
|
}
|
|
|
|
}
|
2021-10-28 03:25:17 +00:00
|
|
|
|
2022-07-13 06:30:54 +00:00
|
|
|
tagIDs, err := rel.tags(ctx)
|
2021-10-28 03:25:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-07-12 00:53:46 +00:00
|
|
|
if addSkipSingleNamePerformerTag && options.SkipSingleNamePerformerTag != nil {
|
2023-07-11 04:37:00 +00:00
|
|
|
tagID, err := strconv.ParseInt(*options.SkipSingleNamePerformerTag, 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error converting tag ID %s: %w", *options.SkipSingleNamePerformerTag, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
tagIDs = intslice.IntAppendUnique(tagIDs, int(tagID))
|
|
|
|
}
|
2022-07-13 06:30:54 +00:00
|
|
|
if tagIDs != nil {
|
|
|
|
ret.Partial.TagIDs = &models.UpdateIDs{
|
|
|
|
IDs: tagIDs,
|
|
|
|
Mode: models.RelationshipUpdateModeSet,
|
|
|
|
}
|
|
|
|
}
|
2021-10-28 03:25:17 +00:00
|
|
|
|
2022-07-13 06:30:54 +00:00
|
|
|
stashIDs, err := rel.stashIDs(ctx)
|
2021-10-28 03:25:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-07-13 06:30:54 +00:00
|
|
|
if stashIDs != nil {
|
|
|
|
ret.Partial.StashIDs = &models.UpdateStashIDs{
|
|
|
|
StashIDs: stashIDs,
|
|
|
|
Mode: models.RelationshipUpdateModeSet,
|
|
|
|
}
|
|
|
|
}
|
2021-10-28 03:25:17 +00:00
|
|
|
|
2023-07-12 00:53:46 +00:00
|
|
|
if utils.IsTrue(options.SetCoverImage) {
|
2021-10-28 03:25:17 +00:00
|
|
|
ret.CoverImage, err = rel.cover(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret, nil
|
|
|
|
}
|
|
|
|
|
2022-05-19 07:49:32 +00:00
|
|
|
func (t *SceneIdentifier) modifyScene(ctx context.Context, txnManager txn.Manager, s *models.Scene, result *scrapeResult) error {
|
2021-11-05 22:58:52 +00:00
|
|
|
var updater *scene.UpdateSet
|
2022-05-19 07:49:32 +00:00
|
|
|
if err := txn.WithTxn(ctx, txnManager, func(ctx context.Context) error {
|
2022-08-12 02:21:46 +00:00
|
|
|
// load scene relationships
|
|
|
|
if err := s.LoadPerformerIDs(ctx, t.SceneReaderUpdater); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := s.LoadTagIDs(ctx, t.SceneReaderUpdater); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := s.LoadStashIDs(ctx, t.SceneReaderUpdater); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-11-05 22:58:52 +00:00
|
|
|
var err error
|
2022-05-19 07:49:32 +00:00
|
|
|
updater, err = t.getSceneUpdater(ctx, s, result)
|
2021-11-05 22:58:52 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-10-28 03:25:17 +00:00
|
|
|
|
2021-11-05 22:58:52 +00:00
|
|
|
// don't update anything if nothing was set
|
|
|
|
if updater.IsEmpty() {
|
2022-09-01 07:54:34 +00:00
|
|
|
logger.Debugf("Nothing to set for %s", s.Path)
|
2021-11-05 22:58:52 +00:00
|
|
|
return nil
|
|
|
|
}
|
2021-10-28 03:25:17 +00:00
|
|
|
|
2023-03-16 23:52:49 +00:00
|
|
|
if _, err := updater.Update(ctx, t.SceneReaderUpdater); err != nil {
|
2021-11-05 22:58:52 +00:00
|
|
|
return fmt.Errorf("error updating scene: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
as := ""
|
|
|
|
title := updater.Partial.Title
|
2022-07-13 06:30:54 +00:00
|
|
|
if title.Ptr() != nil {
|
|
|
|
as = fmt.Sprintf(" as %s", title.Value)
|
2021-11-05 22:58:52 +00:00
|
|
|
}
|
2022-09-01 07:54:34 +00:00
|
|
|
logger.Infof("Successfully identified %s%s using %s", s.Path, as, result.source.Name)
|
2021-11-05 22:58:52 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}); err != nil {
|
|
|
|
return err
|
2021-10-28 03:25:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// fire post-update hooks
|
2021-11-05 22:58:52 +00:00
|
|
|
if !updater.IsEmpty() {
|
|
|
|
updateInput := updater.UpdateInput()
|
|
|
|
fields := utils.NotNilFields(updateInput, "json")
|
|
|
|
t.SceneUpdatePostHookExecutor.ExecuteSceneUpdatePostHooks(ctx, updateInput, fields)
|
2021-10-28 03:25:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-07-11 04:37:00 +00:00
|
|
|
func (t *SceneIdentifier) addTagToScene(ctx context.Context, txnManager txn.Manager, s *models.Scene, tagToAdd string) error {
|
|
|
|
if err := txn.WithTxn(ctx, txnManager, func(ctx context.Context) error {
|
|
|
|
tagID, err := strconv.Atoi(tagToAdd)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error converting tag ID %s: %w", tagToAdd, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.LoadTagIDs(ctx, t.SceneReaderUpdater); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
existing := s.TagIDs.List()
|
|
|
|
|
|
|
|
if intslice.IntInclude(existing, tagID) {
|
|
|
|
// skip if the scene was already tagged
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := scene.AddTag(ctx, t.SceneReaderUpdater, s, tagID); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ret, err := t.TagCreatorFinder.Find(ctx, tagID)
|
|
|
|
if err != nil {
|
|
|
|
logger.Infof("Added tag id %s to skipped scene %s", tagToAdd, s.Path)
|
|
|
|
} else {
|
|
|
|
logger.Infof("Added tag %s to skipped scene %s", ret.Name, s.Path)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-04-25 05:55:05 +00:00
|
|
|
func getFieldOptions(options []MetadataOptions) map[string]*FieldOptions {
|
2021-10-28 03:25:17 +00:00
|
|
|
// prefer source-specific field strategies, then the defaults
|
2022-04-25 05:55:05 +00:00
|
|
|
ret := make(map[string]*FieldOptions)
|
2021-10-28 03:25:17 +00:00
|
|
|
for _, oo := range options {
|
|
|
|
for _, f := range oo.FieldOptions {
|
|
|
|
if _, found := ret[f.Field]; !found {
|
|
|
|
ret[f.Field] = f
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2022-04-25 05:55:05 +00:00
|
|
|
func getScenePartial(scene *models.Scene, scraped *scraper.ScrapedScene, fieldOptions map[string]*FieldOptions, setOrganized bool) models.ScenePartial {
|
2022-07-13 06:30:54 +00:00
|
|
|
partial := models.ScenePartial{}
|
2021-10-28 03:25:17 +00:00
|
|
|
|
2022-07-13 06:30:54 +00:00
|
|
|
if scraped.Title != nil && (scene.Title != *scraped.Title) {
|
|
|
|
if shouldSetSingleValueField(fieldOptions["title"], scene.Title != "") {
|
|
|
|
partial.Title = models.NewOptionalString(*scraped.Title)
|
2021-10-28 03:25:17 +00:00
|
|
|
}
|
|
|
|
}
|
2022-07-13 06:30:54 +00:00
|
|
|
if scraped.Date != nil && (scene.Date == nil || scene.Date.String() != *scraped.Date) {
|
|
|
|
if shouldSetSingleValueField(fieldOptions["date"], scene.Date != nil) {
|
|
|
|
d := models.NewDate(*scraped.Date)
|
|
|
|
partial.Date = models.NewOptionalDate(d)
|
2021-10-28 03:25:17 +00:00
|
|
|
}
|
|
|
|
}
|
2022-07-13 06:30:54 +00:00
|
|
|
if scraped.Details != nil && (scene.Details != *scraped.Details) {
|
|
|
|
if shouldSetSingleValueField(fieldOptions["details"], scene.Details != "") {
|
|
|
|
partial.Details = models.NewOptionalString(*scraped.Details)
|
2021-10-28 03:25:17 +00:00
|
|
|
}
|
|
|
|
}
|
2022-07-13 06:30:54 +00:00
|
|
|
if scraped.URL != nil && (scene.URL != *scraped.URL) {
|
|
|
|
if shouldSetSingleValueField(fieldOptions["url"], scene.URL != "") {
|
|
|
|
partial.URL = models.NewOptionalString(*scraped.URL)
|
2021-10-28 03:25:17 +00:00
|
|
|
}
|
|
|
|
}
|
2022-11-10 01:51:49 +00:00
|
|
|
if scraped.Director != nil && (scene.Director != *scraped.Director) {
|
|
|
|
if shouldSetSingleValueField(fieldOptions["director"], scene.Director != "") {
|
|
|
|
partial.Director = models.NewOptionalString(*scraped.Director)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if scraped.Code != nil && (scene.Code != *scraped.Code) {
|
|
|
|
if shouldSetSingleValueField(fieldOptions["code"], scene.Code != "") {
|
|
|
|
partial.Code = models.NewOptionalString(*scraped.Code)
|
|
|
|
}
|
|
|
|
}
|
2021-10-28 03:25:17 +00:00
|
|
|
|
|
|
|
if setOrganized && !scene.Organized {
|
2023-07-11 04:37:00 +00:00
|
|
|
partial.Organized = models.NewOptionalBool(true)
|
2021-10-28 03:25:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return partial
|
|
|
|
}
|
|
|
|
|
2022-04-25 05:55:05 +00:00
|
|
|
func shouldSetSingleValueField(strategy *FieldOptions, hasExistingValue bool) bool {
|
2021-10-28 03:25:17 +00:00
|
|
|
// if unset then default to MERGE
|
2022-04-25 05:55:05 +00:00
|
|
|
fs := FieldStrategyMerge
|
2021-10-28 03:25:17 +00:00
|
|
|
|
|
|
|
if strategy != nil && strategy.Strategy.IsValid() {
|
|
|
|
fs = strategy.Strategy
|
|
|
|
}
|
|
|
|
|
2022-04-25 05:55:05 +00:00
|
|
|
if fs == FieldStrategyIgnore {
|
2021-10-28 03:25:17 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2022-04-25 05:55:05 +00:00
|
|
|
return !hasExistingValue || fs == FieldStrategyOverwrite
|
2021-10-28 03:25:17 +00:00
|
|
|
}
|