From c6bcdd89be67399221a3e217fc8deb185d361451 Mon Sep 17 00:00:00 2001 From: its-josh4 <74079536+its-josh4@users.noreply.github.com> Date: Mon, 28 Oct 2024 17:26:23 -0700 Subject: [PATCH] Use slices package from the stdlib when possible (#5360) * Use slices from the stdlib when possible * Add some unit tests * More small tweaks + add benchmark func --- internal/api/resolver_query_find_image.go | 8 +- internal/api/resolver_query_find_scene.go | 14 ++-- internal/api/resolver_query_package.go | 4 +- internal/autotag/gallery.go | 6 +- internal/autotag/image.go | 6 +- internal/autotag/performer.go | 8 +- internal/autotag/scene.go | 6 +- internal/autotag/tag.go | 8 +- internal/dlna/cds.go | 4 +- internal/dlna/whitelist.go | 5 +- internal/identify/identify.go | 3 +- internal/identify/identify_test.go | 4 +- pkg/gallery/import.go | 5 +- pkg/group/import.go | 3 +- pkg/group/validate.go | 3 +- pkg/image/import.go | 5 +- pkg/image/scan.go | 4 +- pkg/performer/import.go | 3 +- pkg/plugin/plugins.go | 10 +-- pkg/scene/import.go | 5 +- pkg/scene/merge.go | 3 +- pkg/sliceutil/collections.go | 48 +++++------ pkg/sliceutil/collections_test.go | 83 +++++++++++++++++++ .../stringslice/string_collections.go | 4 +- pkg/sqlite/gallery.go | 4 +- pkg/sqlite/gallery_chapter.go | 4 +- pkg/sqlite/group.go | 4 +- pkg/sqlite/group_test.go | 9 +- pkg/sqlite/image.go | 3 +- pkg/sqlite/performer.go | 4 +- pkg/sqlite/saved_filter.go | 4 +- pkg/sqlite/scene.go | 3 +- pkg/sqlite/scene_marker.go | 4 +- pkg/sqlite/scene_marker_test.go | 4 +- pkg/sqlite/setup_test.go | 4 +- pkg/sqlite/studio.go | 4 +- pkg/sqlite/tag.go | 4 +- pkg/studio/import.go | 3 +- 38 files changed, 200 insertions(+), 110 deletions(-) diff --git a/internal/api/resolver_query_find_image.go b/internal/api/resolver_query_find_image.go index b3d674613..48b926345 100644 --- a/internal/api/resolver_query_find_image.go +++ b/internal/api/resolver_query_find_image.go @@ -2,11 +2,11 @@ package api import ( "context" + "slices" "strconv" "github.com/99designs/gqlgen/graphql" "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/sliceutil" "github.com/stashapp/stash/pkg/sliceutil/stringslice" ) @@ -95,11 +95,11 @@ func (r *queryResolver) FindImages( result, err = qb.Query(ctx, models.ImageQueryOptions{ QueryOptions: models.QueryOptions{ FindFilter: filter, - Count: sliceutil.Contains(fields, "count"), + Count: slices.Contains(fields, "count"), }, ImageFilter: imageFilter, - Megapixels: sliceutil.Contains(fields, "megapixels"), - TotalSize: sliceutil.Contains(fields, "filesize"), + Megapixels: slices.Contains(fields, "megapixels"), + TotalSize: slices.Contains(fields, "filesize"), }) if err == nil { images, err = result.Resolve(ctx) diff --git a/internal/api/resolver_query_find_scene.go b/internal/api/resolver_query_find_scene.go index 0ea35a490..44b5cfd5e 100644 --- a/internal/api/resolver_query_find_scene.go +++ b/internal/api/resolver_query_find_scene.go @@ -2,13 +2,13 @@ package api import ( "context" + "slices" "strconv" "github.com/99designs/gqlgen/graphql" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/scene" - "github.com/stashapp/stash/pkg/sliceutil" "github.com/stashapp/stash/pkg/sliceutil/stringslice" ) @@ -119,11 +119,11 @@ func (r *queryResolver) FindScenes( result, err = r.repository.Scene.Query(ctx, models.SceneQueryOptions{ QueryOptions: models.QueryOptions{ FindFilter: filter, - Count: sliceutil.Contains(fields, "count"), + Count: slices.Contains(fields, "count"), }, SceneFilter: sceneFilter, - TotalDuration: sliceutil.Contains(fields, "duration"), - TotalSize: sliceutil.Contains(fields, "filesize"), + TotalDuration: slices.Contains(fields, "duration"), + TotalSize: slices.Contains(fields, "filesize"), }) if err == nil { scenes, err = result.Resolve(ctx) @@ -174,11 +174,11 @@ func (r *queryResolver) FindScenesByPathRegex(ctx context.Context, filter *model result, err := r.repository.Scene.Query(ctx, models.SceneQueryOptions{ QueryOptions: models.QueryOptions{ FindFilter: queryFilter, - Count: sliceutil.Contains(fields, "count"), + Count: slices.Contains(fields, "count"), }, SceneFilter: sceneFilter, - TotalDuration: sliceutil.Contains(fields, "duration"), - TotalSize: sliceutil.Contains(fields, "filesize"), + TotalDuration: slices.Contains(fields, "duration"), + TotalSize: slices.Contains(fields, "filesize"), }) if err != nil { return err diff --git a/internal/api/resolver_query_package.go b/internal/api/resolver_query_package.go index 5a42221d4..7e7724132 100644 --- a/internal/api/resolver_query_package.go +++ b/internal/api/resolver_query_package.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "slices" "sort" "strings" @@ -11,7 +12,6 @@ import ( "github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/pkg" - "github.com/stashapp/stash/pkg/sliceutil" ) var ErrInvalidPackageType = errors.New("invalid package type") @@ -166,7 +166,7 @@ func (r *queryResolver) InstalledPackages(ctx context.Context, typeArg PackageTy var ret []*Package - if sliceutil.Contains(graphql.CollectAllFields(ctx), "source_package") { + if slices.Contains(graphql.CollectAllFields(ctx), "source_package") { ret, err = r.getInstalledPackagesWithUpgrades(ctx, pm) if err != nil { return nil, err diff --git a/internal/autotag/gallery.go b/internal/autotag/gallery.go index cbf5ebf09..031079e49 100644 --- a/internal/autotag/gallery.go +++ b/internal/autotag/gallery.go @@ -2,11 +2,11 @@ package autotag import ( "context" + "slices" "github.com/stashapp/stash/pkg/gallery" "github.com/stashapp/stash/pkg/match" "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/sliceutil" ) type GalleryFinderUpdater interface { @@ -53,7 +53,7 @@ func GalleryPerformers(ctx context.Context, s *models.Gallery, rw GalleryPerform } existing := s.PerformerIDs.List() - if sliceutil.Contains(existing, otherID) { + if slices.Contains(existing, otherID) { return false, nil } @@ -91,7 +91,7 @@ func GalleryTags(ctx context.Context, s *models.Gallery, rw GalleryTagUpdater, t } existing := s.TagIDs.List() - if sliceutil.Contains(existing, otherID) { + if slices.Contains(existing, otherID) { return false, nil } diff --git a/internal/autotag/image.go b/internal/autotag/image.go index 63544123a..e4acbcd3a 100644 --- a/internal/autotag/image.go +++ b/internal/autotag/image.go @@ -2,11 +2,11 @@ package autotag import ( "context" + "slices" "github.com/stashapp/stash/pkg/image" "github.com/stashapp/stash/pkg/match" "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/sliceutil" ) type ImageFinderUpdater interface { @@ -44,7 +44,7 @@ func ImagePerformers(ctx context.Context, s *models.Image, rw ImagePerformerUpda } existing := s.PerformerIDs.List() - if sliceutil.Contains(existing, otherID) { + if slices.Contains(existing, otherID) { return false, nil } @@ -82,7 +82,7 @@ func ImageTags(ctx context.Context, s *models.Image, rw ImageTagUpdater, tagRead } existing := s.TagIDs.List() - if sliceutil.Contains(existing, otherID) { + if slices.Contains(existing, otherID) { return false, nil } diff --git a/internal/autotag/performer.go b/internal/autotag/performer.go index 12dac0e93..7badda390 100644 --- a/internal/autotag/performer.go +++ b/internal/autotag/performer.go @@ -2,13 +2,13 @@ package autotag import ( "context" + "slices" "github.com/stashapp/stash/pkg/gallery" "github.com/stashapp/stash/pkg/image" "github.com/stashapp/stash/pkg/match" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/scene" - "github.com/stashapp/stash/pkg/sliceutil" "github.com/stashapp/stash/pkg/txn" ) @@ -63,7 +63,7 @@ func (tagger *Tagger) PerformerScenes(ctx context.Context, p *models.Performer, } existing := o.PerformerIDs.List() - if sliceutil.Contains(existing, p.ID) { + if slices.Contains(existing, p.ID) { return false, nil } @@ -92,7 +92,7 @@ func (tagger *Tagger) PerformerImages(ctx context.Context, p *models.Performer, } existing := o.PerformerIDs.List() - if sliceutil.Contains(existing, p.ID) { + if slices.Contains(existing, p.ID) { return false, nil } @@ -121,7 +121,7 @@ func (tagger *Tagger) PerformerGalleries(ctx context.Context, p *models.Performe } existing := o.PerformerIDs.List() - if sliceutil.Contains(existing, p.ID) { + if slices.Contains(existing, p.ID) { return false, nil } diff --git a/internal/autotag/scene.go b/internal/autotag/scene.go index 751d0ed62..273378b9b 100644 --- a/internal/autotag/scene.go +++ b/internal/autotag/scene.go @@ -2,11 +2,11 @@ package autotag import ( "context" + "slices" "github.com/stashapp/stash/pkg/match" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/scene" - "github.com/stashapp/stash/pkg/sliceutil" ) type SceneFinderUpdater interface { @@ -44,7 +44,7 @@ func ScenePerformers(ctx context.Context, s *models.Scene, rw ScenePerformerUpda } existing := s.PerformerIDs.List() - if sliceutil.Contains(existing, otherID) { + if slices.Contains(existing, otherID) { return false, nil } @@ -82,7 +82,7 @@ func SceneTags(ctx context.Context, s *models.Scene, rw SceneTagUpdater, tagRead } existing := s.TagIDs.List() - if sliceutil.Contains(existing, otherID) { + if slices.Contains(existing, otherID) { return false, nil } diff --git a/internal/autotag/tag.go b/internal/autotag/tag.go index 5b1b5c319..4ebbf28a3 100644 --- a/internal/autotag/tag.go +++ b/internal/autotag/tag.go @@ -2,13 +2,13 @@ package autotag import ( "context" + "slices" "github.com/stashapp/stash/pkg/gallery" "github.com/stashapp/stash/pkg/image" "github.com/stashapp/stash/pkg/match" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/scene" - "github.com/stashapp/stash/pkg/sliceutil" "github.com/stashapp/stash/pkg/txn" ) @@ -61,7 +61,7 @@ func (tagger *Tagger) TagScenes(ctx context.Context, p *models.Tag, paths []stri } existing := o.TagIDs.List() - if sliceutil.Contains(existing, p.ID) { + if slices.Contains(existing, p.ID) { return false, nil } @@ -90,7 +90,7 @@ func (tagger *Tagger) TagImages(ctx context.Context, p *models.Tag, paths []stri } existing := o.TagIDs.List() - if sliceutil.Contains(existing, p.ID) { + if slices.Contains(existing, p.ID) { return false, nil } @@ -119,7 +119,7 @@ func (tagger *Tagger) TagGalleries(ctx context.Context, p *models.Tag, paths []s } existing := o.TagIDs.List() - if sliceutil.Contains(existing, p.ID) { + if slices.Contains(existing, p.ID) { return false, nil } diff --git a/internal/dlna/cds.go b/internal/dlna/cds.go index a38e0e55b..034ebbbc1 100644 --- a/internal/dlna/cds.go +++ b/internal/dlna/cds.go @@ -30,6 +30,7 @@ import ( "os" "path" "path/filepath" + "slices" "strconv" "strings" "time" @@ -40,7 +41,6 @@ import ( "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/scene" - "github.com/stashapp/stash/pkg/sliceutil" ) var pageSize = 100 @@ -521,7 +521,7 @@ func (me *contentDirectoryService) getPageVideos(sceneFilter *models.SceneFilter } func getPageFromID(paths []string) *int { - i := sliceutil.Index(paths, "page") + i := slices.Index(paths, "page") if i == -1 || i+1 >= len(paths) { return nil } diff --git a/internal/dlna/whitelist.go b/internal/dlna/whitelist.go index 609ecd38f..e423ec58b 100644 --- a/internal/dlna/whitelist.go +++ b/internal/dlna/whitelist.go @@ -1,10 +1,9 @@ package dlna import ( + "slices" "sync" "time" - - "github.com/stashapp/stash/pkg/sliceutil" ) // only keep the 10 most recent IP addresses @@ -30,7 +29,7 @@ func (m *ipWhitelistManager) addRecent(addr string) bool { m.mutex.Lock() defer m.mutex.Unlock() - i := sliceutil.Index(m.recentIPAddresses, addr) + i := slices.Index(m.recentIPAddresses, addr) if i != -1 { if i == 0 { // don't do anything if it's already at the start diff --git a/internal/identify/identify.go b/internal/identify/identify.go index 5eecd0d99..dca1a68d7 100644 --- a/internal/identify/identify.go +++ b/internal/identify/identify.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "slices" "strconv" "github.com/stashapp/stash/pkg/logger" @@ -333,7 +334,7 @@ func (t *SceneIdentifier) addTagToScene(ctx context.Context, s *models.Scene, ta } existing := s.TagIDs.List() - if sliceutil.Contains(existing, tagID) { + if slices.Contains(existing, tagID) { // skip if the scene was already tagged return nil } diff --git a/internal/identify/identify_test.go b/internal/identify/identify_test.go index 5dc339eac..4d8c6e212 100644 --- a/internal/identify/identify_test.go +++ b/internal/identify/identify_test.go @@ -4,13 +4,13 @@ import ( "context" "errors" "reflect" + "slices" "strconv" "testing" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models/mocks" "github.com/stashapp/stash/pkg/scraper" - "github.com/stashapp/stash/pkg/sliceutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -23,7 +23,7 @@ type mockSceneScraper struct { } func (s mockSceneScraper) ScrapeScenes(ctx context.Context, sceneID int) ([]*scraper.ScrapedScene, error) { - if sliceutil.Contains(s.errIDs, sceneID) { + if slices.Contains(s.errIDs, sceneID) { return nil, errors.New("scrape scene error") } return s.results[sceneID], nil diff --git a/pkg/gallery/import.go b/pkg/gallery/import.go index c332e0ce0..aaf37bd27 100644 --- a/pkg/gallery/import.go +++ b/pkg/gallery/import.go @@ -3,6 +3,7 @@ package gallery import ( "context" "fmt" + "slices" "strings" "github.com/stashapp/stash/pkg/models" @@ -153,7 +154,7 @@ func (i *Importer) populatePerformers(ctx context.Context) error { } missingPerformers := sliceutil.Filter(names, func(name string) bool { - return !sliceutil.Contains(pluckedNames, name) + return !slices.Contains(pluckedNames, name) }) if len(missingPerformers) > 0 { @@ -212,7 +213,7 @@ func (i *Importer) populateTags(ctx context.Context) error { } missingTags := sliceutil.Filter(names, func(name string) bool { - return !sliceutil.Contains(pluckedNames, name) + return !slices.Contains(pluckedNames, name) }) if len(missingTags) > 0 { diff --git a/pkg/group/import.go b/pkg/group/import.go index 589e75df3..3fc7db8f1 100644 --- a/pkg/group/import.go +++ b/pkg/group/import.go @@ -3,6 +3,7 @@ package group import ( "context" "fmt" + "slices" "strings" "github.com/stashapp/stash/pkg/models" @@ -96,7 +97,7 @@ func importTags(ctx context.Context, tagWriter models.TagFinderCreator, names [] } missingTags := sliceutil.Filter(names, func(name string) bool { - return !sliceutil.Contains(pluckedNames, name) + return !slices.Contains(pluckedNames, name) }) if len(missingTags) > 0 { diff --git a/pkg/group/validate.go b/pkg/group/validate.go index 723b9f699..255152a95 100644 --- a/pkg/group/validate.go +++ b/pkg/group/validate.go @@ -2,6 +2,7 @@ package group import ( "context" + "slices" "strings" "github.com/stashapp/stash/pkg/models" @@ -105,7 +106,7 @@ func (s *Service) validateUpdateGroupHierarchy(ctx context.Context, existing *mo subIDs := idsFromGroupDescriptions(effectiveSubGroups) // ensure we haven't set the group as a subgroup of itself - if sliceutil.Contains(containingIDs, existing.ID) || sliceutil.Contains(subIDs, existing.ID) { + if slices.Contains(containingIDs, existing.ID) || slices.Contains(subIDs, existing.ID) { return ErrHierarchyLoop } diff --git a/pkg/image/import.go b/pkg/image/import.go index fa8fe2161..660eb1da1 100644 --- a/pkg/image/import.go +++ b/pkg/image/import.go @@ -3,6 +3,7 @@ package image import ( "context" "fmt" + "slices" "strings" "github.com/stashapp/stash/pkg/models" @@ -239,7 +240,7 @@ func (i *Importer) populatePerformers(ctx context.Context) error { } missingPerformers := sliceutil.Filter(names, func(name string) bool { - return !sliceutil.Contains(pluckedNames, name) + return !slices.Contains(pluckedNames, name) }) if len(missingPerformers) > 0 { @@ -375,7 +376,7 @@ func importTags(ctx context.Context, tagWriter models.TagFinderCreator, names [] } missingTags := sliceutil.Filter(names, func(name string) bool { - return !sliceutil.Contains(pluckedNames, name) + return !slices.Contains(pluckedNames, name) }) if len(missingTags) > 0 { diff --git a/pkg/image/scan.go b/pkg/image/scan.go index b388a8145..a6002057f 100644 --- a/pkg/image/scan.go +++ b/pkg/image/scan.go @@ -6,13 +6,13 @@ import ( "fmt" "os" "path/filepath" + "slices" "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models/paths" "github.com/stashapp/stash/pkg/plugin" "github.com/stashapp/stash/pkg/plugin/hook" - "github.com/stashapp/stash/pkg/sliceutil" "github.com/stashapp/stash/pkg/txn" ) @@ -356,7 +356,7 @@ func (h *ScanHandler) getGalleryToAssociate(ctx context.Context, newImage *model return nil, err } - if g != nil && !sliceutil.Contains(newImage.GalleryIDs.List(), g.ID) { + if g != nil && !slices.Contains(newImage.GalleryIDs.List(), g.ID) { return g, nil } diff --git a/pkg/performer/import.go b/pkg/performer/import.go index d50384fa3..49a2ce291 100644 --- a/pkg/performer/import.go +++ b/pkg/performer/import.go @@ -3,6 +3,7 @@ package performer import ( "context" "fmt" + "slices" "strconv" "strings" @@ -75,7 +76,7 @@ func importTags(ctx context.Context, tagWriter models.TagFinderCreator, names [] } missingTags := sliceutil.Filter(names, func(name string) bool { - return !sliceutil.Contains(pluckedNames, name) + return !slices.Contains(pluckedNames, name) }) if len(missingTags) > 0 { diff --git a/pkg/plugin/plugins.go b/pkg/plugin/plugins.go index 38cde68bc..9671f8901 100644 --- a/pkg/plugin/plugins.go +++ b/pkg/plugin/plugins.go @@ -14,6 +14,7 @@ import ( "net/http" "os" "path/filepath" + "slices" "strconv" "strings" @@ -23,7 +24,6 @@ import ( "github.com/stashapp/stash/pkg/plugin/common" "github.com/stashapp/stash/pkg/plugin/hook" "github.com/stashapp/stash/pkg/session" - "github.com/stashapp/stash/pkg/sliceutil" "github.com/stashapp/stash/pkg/txn" "github.com/stashapp/stash/pkg/utils" ) @@ -168,7 +168,7 @@ func (c Cache) enabledPlugins() []Config { var ret []Config for _, p := range c.plugins { - disabled := sliceutil.Contains(disabledPlugins, p.id) + disabled := slices.Contains(disabledPlugins, p.id) if !disabled { ret = append(ret, p) @@ -181,7 +181,7 @@ func (c Cache) enabledPlugins() []Config { func (c Cache) pluginDisabled(id string) bool { disabledPlugins := c.config.GetDisabledPlugins() - return sliceutil.Contains(disabledPlugins, id) + return slices.Contains(disabledPlugins, id) } // ListPlugins returns plugin details for all of the loaded plugins. @@ -192,7 +192,7 @@ func (c Cache) ListPlugins() []*Plugin { for _, s := range c.plugins { p := s.toPlugin() - disabled := sliceutil.Contains(disabledPlugins, p.ID) + disabled := slices.Contains(disabledPlugins, p.ID) p.Enabled = !disabled ret = append(ret, p) @@ -209,7 +209,7 @@ func (c Cache) GetPlugin(id string) *Plugin { if plugin != nil { p := plugin.toPlugin() - disabled := sliceutil.Contains(disabledPlugins, p.ID) + disabled := slices.Contains(disabledPlugins, p.ID) p.Enabled = !disabled return p } diff --git a/pkg/scene/import.go b/pkg/scene/import.go index b36e1bd68..c1b065bcf 100644 --- a/pkg/scene/import.go +++ b/pkg/scene/import.go @@ -3,6 +3,7 @@ package scene import ( "context" "fmt" + "slices" "strings" "time" @@ -290,7 +291,7 @@ func (i *Importer) populatePerformers(ctx context.Context) error { } missingPerformers := sliceutil.Filter(names, func(name string) bool { - return !sliceutil.Contains(pluckedNames, name) + return !slices.Contains(pluckedNames, name) }) if len(missingPerformers) > 0 { @@ -517,7 +518,7 @@ func importTags(ctx context.Context, tagWriter models.TagFinderCreator, names [] } missingTags := sliceutil.Filter(names, func(name string) bool { - return !sliceutil.Contains(pluckedNames, name) + return !slices.Contains(pluckedNames, name) }) if len(missingTags) > 0 { diff --git a/pkg/scene/merge.go b/pkg/scene/merge.go index e7c9ab2f7..77b551ab2 100644 --- a/pkg/scene/merge.go +++ b/pkg/scene/merge.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "path/filepath" + "slices" "time" "github.com/stashapp/stash/pkg/fsutil" @@ -28,7 +29,7 @@ func (s *Service) Merge(ctx context.Context, sourceIDs []int, destinationID int, sourceIDs = sliceutil.AppendUniques(nil, sourceIDs) // ensure destination is not in source list - if sliceutil.Contains(sourceIDs, destinationID) { + if slices.Contains(sourceIDs, destinationID) { return errors.New("destination scene cannot be in source list") } diff --git a/pkg/sliceutil/collections.go b/pkg/sliceutil/collections.go index 18930df25..eff28fc40 100644 --- a/pkg/sliceutil/collections.go +++ b/pkg/sliceutil/collections.go @@ -1,26 +1,14 @@ // Package sliceutil provides utilities for working with slices. package sliceutil -// Index returns the first index of the provided value in the provided -// slice. It returns -1 if it is not found. -func Index[T comparable](vs []T, t T) int { - for i, v := range vs { - if v == t { - return i - } - } - return -1 -} - -// Contains returns whether the vs slice contains t. -func Contains[T comparable](vs []T, t T) bool { - return Index(vs, t) >= 0 -} +import ( + "slices" +) // AppendUnique appends toAdd to the vs slice if toAdd does not already // exist in the slice. It returns the new or unchanged slice. func AppendUnique[T comparable](vs []T, toAdd T) []T { - if Contains(vs, toAdd) { + if slices.Contains(vs, toAdd) { return vs } @@ -31,6 +19,13 @@ func AppendUnique[T comparable](vs []T, toAdd T) []T { // appends values that do not already exist in the slice. // It returns the new or unchanged slice. func AppendUniques[T comparable](vs []T, toAdd []T) []T { + if len(toAdd) == 0 { + return vs + } + + // Extend the slice's capacity to avoid multiple re-allocations even in the worst case + vs = slices.Grow(vs, len(toAdd)) + for _, v := range toAdd { vs = AppendUnique(vs, v) } @@ -41,9 +36,9 @@ func AppendUniques[T comparable](vs []T, toAdd []T) []T { // Exclude returns a copy of the vs slice, excluding all values // that are also present in the toExclude slice. func Exclude[T comparable](vs []T, toExclude []T) []T { - var ret []T + ret := make([]T, 0, len(vs)) for _, v := range vs { - if !Contains(toExclude, v) { + if !slices.Contains(toExclude, v) { ret = append(ret, v) } } @@ -53,8 +48,8 @@ func Exclude[T comparable](vs []T, toExclude []T) []T { // Unique returns a copy of the vs slice, with non-unique values removed. func Unique[T comparable](vs []T) []T { - distinctValues := make(map[T]struct{}) - var ret []T + distinctValues := make(map[T]struct{}, len(vs)) + ret := make([]T, 0, len(vs)) for _, v := range vs { if _, exists := distinctValues[v]; !exists { distinctValues[v] = struct{}{} @@ -66,7 +61,7 @@ func Unique[T comparable](vs []T) []T { // Delete returns a copy of the vs slice with toDel values removed. func Delete[T comparable](vs []T, toDel T) []T { - var ret []T + ret := make([]T, 0, len(vs)) for _, v := range vs { if v != toDel { ret = append(ret, v) @@ -79,7 +74,7 @@ func Delete[T comparable](vs []T, toDel T) []T { func Intersect[T comparable](a []T, b []T) []T { var ret []T for _, v := range a { - if Contains(b, v) { + if slices.Contains(b, v) { ret = append(ret, v) } } @@ -91,13 +86,13 @@ func Intersect[T comparable](a []T, b []T) []T { func NotIntersect[T comparable](a []T, b []T) []T { var ret []T for _, v := range a { - if !Contains(b, v) { + if !slices.Contains(b, v) { ret = append(ret, v) } } for _, v := range b { - if !Contains(a, v) { + if !slices.Contains(a, v) { ret = append(ret, v) } } @@ -166,8 +161,9 @@ func PtrsToValues[T any](vs []*T) []T { func ValuesToPtrs[T any](vs []T) []*T { ret := make([]*T, len(vs)) for i, v := range vs { - vv := v - ret[i] = &vv + // We can do this safely because go.mod indicates Go 1.22 + // See: https://go.dev/blog/loopvar-preview + ret[i] = &v } return ret } diff --git a/pkg/sliceutil/collections_test.go b/pkg/sliceutil/collections_test.go index 70d34946f..ab739607e 100644 --- a/pkg/sliceutil/collections_test.go +++ b/pkg/sliceutil/collections_test.go @@ -1,6 +1,7 @@ package sliceutil import ( + "reflect" "testing" "github.com/stretchr/testify/assert" @@ -66,3 +67,85 @@ func TestSliceSame(t *testing.T) { }) } } + +func TestAppendUniques(t *testing.T) { + type args struct { + vs []int + toAdd []int + } + tests := []struct { + name string + args args + want []int + }{ + { + name: "append to empty slice", + args: args{ + vs: []int{}, + toAdd: []int{1, 2, 3}, + }, + want: []int{1, 2, 3}, + }, + { + name: "append all unique values", + args: args{ + vs: []int{1, 2, 3}, + toAdd: []int{4, 5, 6}, + }, + want: []int{1, 2, 3, 4, 5, 6}, + }, + { + name: "append with some duplicates", + args: args{ + vs: []int{1, 2, 3}, + toAdd: []int{3, 4, 5}, + }, + want: []int{1, 2, 3, 4, 5}, + }, + { + name: "append all duplicates", + args: args{ + vs: []int{1, 2, 3}, + toAdd: []int{1, 2, 3}, + }, + want: []int{1, 2, 3}, + }, + { + name: "append to nil slice", + args: args{ + vs: nil, + toAdd: []int{1, 2, 3}, + }, + want: []int{1, 2, 3}, + }, + { + name: "append empty slice", + args: args{ + vs: []int{1, 2, 3}, + toAdd: []int{}, + }, + want: []int{1, 2, 3}, + }, + { + name: "append nil to slice", + args: args{ + vs: []int{1, 2, 3}, + toAdd: nil, + }, + want: []int{1, 2, 3}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := AppendUniques(tt.args.vs, tt.args.toAdd); !reflect.DeepEqual(got, tt.want) { + t.Errorf("AppendUniques() = %v, want %v", got, tt.want) + } + }) + } +} + +func BenchmarkAppendUniques(b *testing.B) { + for i := 0; i < b.N; i++ { + AppendUniques([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, []int{3, 4, 4, 11, 12, 13, 14, 15, 16, 17, 18}) + } +} diff --git a/pkg/sliceutil/stringslice/string_collections.go b/pkg/sliceutil/stringslice/string_collections.go index e4b9ca6a9..f6ea1361c 100644 --- a/pkg/sliceutil/stringslice/string_collections.go +++ b/pkg/sliceutil/stringslice/string_collections.go @@ -33,8 +33,8 @@ func FromString(s string, sep string) []string { // Unique returns a slice containing only unique values from the provided slice. // The comparison is case-insensitive. func UniqueFold(s []string) []string { - seen := make(map[string]struct{}) - var ret []string + seen := make(map[string]struct{}, len(s)) + ret := make([]string, 0, len(s)) for _, v := range s { if _, exists := seen[strings.ToLower(v)]; exists { continue diff --git a/pkg/sqlite/gallery.go b/pkg/sqlite/gallery.go index 5473b9c36..90e46bebc 100644 --- a/pkg/sqlite/gallery.go +++ b/pkg/sqlite/gallery.go @@ -6,12 +6,12 @@ import ( "errors" "fmt" "path/filepath" + "slices" "github.com/doug-martin/goqu/v9" "github.com/doug-martin/goqu/v9/exp" "github.com/jmoiron/sqlx" "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/sliceutil" "gopkg.in/guregu/null.v4" "gopkg.in/guregu/null.v4/zero" ) @@ -412,7 +412,7 @@ func (qb *GalleryStore) FindMany(ctx context.Context, ids []int) ([]*models.Gall } for _, s := range unsorted { - i := sliceutil.Index(ids, s.ID) + i := slices.Index(ids, s.ID) galleries[i] = s } diff --git a/pkg/sqlite/gallery_chapter.go b/pkg/sqlite/gallery_chapter.go index f0d9c5298..92ae63f5f 100644 --- a/pkg/sqlite/gallery_chapter.go +++ b/pkg/sqlite/gallery_chapter.go @@ -5,13 +5,13 @@ import ( "database/sql" "errors" "fmt" + "slices" "github.com/doug-martin/goqu/v9" "github.com/doug-martin/goqu/v9/exp" "github.com/jmoiron/sqlx" "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/sliceutil" ) const ( @@ -162,7 +162,7 @@ func (qb *GalleryChapterStore) FindMany(ctx context.Context, ids []int) ([]*mode } for _, s := range unsorted { - i := sliceutil.Index(ids, s.ID) + i := slices.Index(ids, s.ID) ret[i] = s } diff --git a/pkg/sqlite/group.go b/pkg/sqlite/group.go index 603494fe7..5a214f818 100644 --- a/pkg/sqlite/group.go +++ b/pkg/sqlite/group.go @@ -5,6 +5,7 @@ import ( "database/sql" "errors" "fmt" + "slices" "github.com/doug-martin/goqu/v9" "github.com/doug-martin/goqu/v9/exp" @@ -13,7 +14,6 @@ import ( "gopkg.in/guregu/null.v4/zero" "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/sliceutil" ) const ( @@ -295,7 +295,7 @@ func (qb *GroupStore) FindMany(ctx context.Context, ids []int) ([]*models.Group, } for _, s := range unsorted { - i := sliceutil.Index(ids, s.ID) + i := slices.Index(ids, s.ID) ret[i] = s } diff --git a/pkg/sqlite/group_test.go b/pkg/sqlite/group_test.go index 1d3637c86..d4a177e86 100644 --- a/pkg/sqlite/group_test.go +++ b/pkg/sqlite/group_test.go @@ -6,6 +6,7 @@ package sqlite_test import ( "context" "fmt" + "slices" "strconv" "strings" "testing" @@ -1605,7 +1606,7 @@ func TestGroupReorderSubGroups(t *testing.T) { // get ids of groups newIDs := sliceutil.Map(gd, func(gd models.GroupIDDescription) int { return gd.GroupID }) - newIdxs := sliceutil.Map(newIDs, func(id int) int { return sliceutil.Index(idxToId, id) }) + newIdxs := sliceutil.Map(newIDs, func(id int) int { return slices.Index(idxToId, id) }) assert.ElementsMatch(t, tt.expectedIdxs, newIdxs) }) @@ -1733,7 +1734,7 @@ func TestGroupAddSubGroups(t *testing.T) { // get ids of groups newIDs := sliceutil.Map(gd, func(gd models.GroupIDDescription) int { return gd.GroupID }) - newIdxs := sliceutil.Map(newIDs, func(id int) int { return sliceutil.Index(idxToId, id) }) + newIdxs := sliceutil.Map(newIDs, func(id int) int { return slices.Index(idxToId, id) }) assert.ElementsMatch(t, tt.expectedIdxs, newIdxs) }) @@ -1828,7 +1829,7 @@ func TestGroupRemoveSubGroups(t *testing.T) { // get ids of groups newIDs := sliceutil.Map(gd, func(gd models.GroupIDDescription) int { return gd.GroupID }) - newIdxs := sliceutil.Map(newIDs, func(id int) int { return sliceutil.Index(idxToId, id) }) + newIdxs := sliceutil.Map(newIDs, func(id int) int { return slices.Index(idxToId, id) }) assert.ElementsMatch(t, tt.expectedIdxs, newIdxs) }) @@ -1883,7 +1884,7 @@ func TestGroupFindSubGroupIDs(t *testing.T) { } // get ids of groups - foundIdxs := sliceutil.Map(found, func(id int) int { return sliceutil.Index(groupIDs, id) }) + foundIdxs := sliceutil.Map(found, func(id int) int { return slices.Index(groupIDs, id) }) assert.ElementsMatch(t, tt.expectedIdxs, foundIdxs) }) diff --git a/pkg/sqlite/image.go b/pkg/sqlite/image.go index 4f61c1777..4bc28fad8 100644 --- a/pkg/sqlite/image.go +++ b/pkg/sqlite/image.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "path/filepath" + "slices" "github.com/jmoiron/sqlx" "github.com/stashapp/stash/pkg/models" @@ -398,7 +399,7 @@ func (qb *ImageStore) FindMany(ctx context.Context, ids []int) ([]*models.Image, } for _, s := range unsorted { - i := sliceutil.Index(ids, s.ID) + i := slices.Index(ids, s.ID) images[i] = s } diff --git a/pkg/sqlite/performer.go b/pkg/sqlite/performer.go index 7ff6f5401..e20dc9c4c 100644 --- a/pkg/sqlite/performer.go +++ b/pkg/sqlite/performer.go @@ -5,12 +5,12 @@ import ( "database/sql" "errors" "fmt" + "slices" "github.com/doug-martin/goqu/v9" "github.com/doug-martin/goqu/v9/exp" "github.com/jmoiron/sqlx" "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/sliceutil" "github.com/stashapp/stash/pkg/utils" "gopkg.in/guregu/null.v4" "gopkg.in/guregu/null.v4/zero" @@ -398,7 +398,7 @@ func (qb *PerformerStore) FindMany(ctx context.Context, ids []int) ([]*models.Pe } for _, s := range unsorted { - i := sliceutil.Index(ids, s.ID) + i := slices.Index(ids, s.ID) ret[i] = s } diff --git a/pkg/sqlite/saved_filter.go b/pkg/sqlite/saved_filter.go index 8f58b05e7..583e24062 100644 --- a/pkg/sqlite/saved_filter.go +++ b/pkg/sqlite/saved_filter.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "slices" "github.com/doug-martin/goqu/v9" "github.com/doug-martin/goqu/v9/exp" @@ -13,7 +14,6 @@ import ( "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/sliceutil" ) const ( @@ -165,7 +165,7 @@ func (qb *SavedFilterStore) FindMany(ctx context.Context, ids []int, ignoreNotFo } for _, s := range unsorted { - i := sliceutil.Index(ids, s.ID) + i := slices.Index(ids, s.ID) ret[i] = s } diff --git a/pkg/sqlite/scene.go b/pkg/sqlite/scene.go index 5df614b88..edd363483 100644 --- a/pkg/sqlite/scene.go +++ b/pkg/sqlite/scene.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "path/filepath" + "slices" "sort" "strconv" "strings" @@ -504,7 +505,7 @@ func (qb *SceneStore) FindMany(ctx context.Context, ids []int) ([]*models.Scene, } for _, s := range unsorted { - i := sliceutil.Index(ids, s.ID) + i := slices.Index(ids, s.ID) scenes[i] = s } diff --git a/pkg/sqlite/scene_marker.go b/pkg/sqlite/scene_marker.go index 87a849d20..4af4d6b4b 100644 --- a/pkg/sqlite/scene_marker.go +++ b/pkg/sqlite/scene_marker.go @@ -5,13 +5,13 @@ import ( "database/sql" "errors" "fmt" + "slices" "github.com/doug-martin/goqu/v9" "github.com/doug-martin/goqu/v9/exp" "github.com/jmoiron/sqlx" "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/sliceutil" ) const sceneMarkerTable = "scene_markers" @@ -188,7 +188,7 @@ func (qb *SceneMarkerStore) FindMany(ctx context.Context, ids []int) ([]*models. } for _, s := range unsorted { - i := sliceutil.Index(ids, s.ID) + i := slices.Index(ids, s.ID) ret[i] = s } diff --git a/pkg/sqlite/scene_marker_test.go b/pkg/sqlite/scene_marker_test.go index 0a8343a8b..ce8f4d3ad 100644 --- a/pkg/sqlite/scene_marker_test.go +++ b/pkg/sqlite/scene_marker_test.go @@ -5,11 +5,11 @@ package sqlite_test import ( "context" + "slices" "strconv" "testing" "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/sliceutil" "github.com/stashapp/stash/pkg/sliceutil/stringslice" "github.com/stretchr/testify/assert" ) @@ -133,7 +133,7 @@ func verifyIDs(t *testing.T, modifier models.CriterionModifier, values []int, re case models.CriterionModifierNotEquals: foundAll := true for _, v := range values { - if !sliceutil.Contains(results, v) { + if !slices.Contains(results, v) { foundAll = false break } diff --git a/pkg/sqlite/setup_test.go b/pkg/sqlite/setup_test.go index 624ffb4e2..1c3f914d3 100644 --- a/pkg/sqlite/setup_test.go +++ b/pkg/sqlite/setup_test.go @@ -10,13 +10,13 @@ import ( "fmt" "os" "path/filepath" + "slices" "strconv" "testing" "time" "github.com/stashapp/stash/internal/manager/config" "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/sliceutil" "github.com/stashapp/stash/pkg/sqlite" "github.com/stashapp/stash/pkg/txn" @@ -1585,7 +1585,7 @@ func getTagMarkerCount(id int) int { count := 0 idx := indexFromID(tagIDs, id) for _, s := range markerSpecs { - if s.primaryTagIdx == idx || sliceutil.Contains(s.tagIdxs, idx) { + if s.primaryTagIdx == idx || slices.Contains(s.tagIdxs, idx) { count++ } } diff --git a/pkg/sqlite/studio.go b/pkg/sqlite/studio.go index 95edf4173..6b81109b1 100644 --- a/pkg/sqlite/studio.go +++ b/pkg/sqlite/studio.go @@ -5,6 +5,7 @@ import ( "database/sql" "errors" "fmt" + "slices" "github.com/doug-martin/goqu/v9" "github.com/doug-martin/goqu/v9/exp" @@ -13,7 +14,6 @@ import ( "gopkg.in/guregu/null.v4/zero" "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/sliceutil" "github.com/stashapp/stash/pkg/studio" ) @@ -305,7 +305,7 @@ func (qb *StudioStore) FindMany(ctx context.Context, ids []int) ([]*models.Studi } for _, s := range unsorted { - i := sliceutil.Index(ids, s.ID) + i := slices.Index(ids, s.ID) ret[i] = s } diff --git a/pkg/sqlite/tag.go b/pkg/sqlite/tag.go index 42bdd9bbe..919ef4847 100644 --- a/pkg/sqlite/tag.go +++ b/pkg/sqlite/tag.go @@ -5,6 +5,7 @@ import ( "database/sql" "errors" "fmt" + "slices" "strings" "github.com/doug-martin/goqu/v9" @@ -14,7 +15,6 @@ import ( "gopkg.in/guregu/null.v4/zero" "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/sliceutil" ) const ( @@ -312,7 +312,7 @@ func (qb *TagStore) FindMany(ctx context.Context, ids []int) ([]*models.Tag, err } for _, s := range unsorted { - i := sliceutil.Index(ids, s.ID) + i := slices.Index(ids, s.ID) ret[i] = s } diff --git a/pkg/studio/import.go b/pkg/studio/import.go index d88065078..3aaceb093 100644 --- a/pkg/studio/import.go +++ b/pkg/studio/import.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "slices" "strings" "github.com/stashapp/stash/pkg/models" @@ -80,7 +81,7 @@ func importTags(ctx context.Context, tagWriter models.TagFinderCreator, names [] } missingTags := sliceutil.Filter(names, func(name string) bool { - return !sliceutil.Contains(pluckedNames, name) + return !slices.Contains(pluckedNames, name) }) if len(missingTags) > 0 {