mirror of
https://github.com/stashapp/stash.git
synced 2025-12-16 20:07:05 +03:00
Genericise sliceutil functions (#4253)
* Genericise sliceutil.SliceSame * Genericise intslice functions * Genericise stringutil functions
This commit is contained in:
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/models/jsonschema"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
)
|
||||
|
||||
type ImporterReaderWriter interface {
|
||||
@@ -146,8 +146,8 @@ func (i *Importer) populatePerformers(ctx context.Context) error {
|
||||
pluckedNames = append(pluckedNames, performer.Name)
|
||||
}
|
||||
|
||||
missingPerformers := stringslice.StrFilter(names, func(name string) bool {
|
||||
return !stringslice.StrInclude(pluckedNames, name)
|
||||
missingPerformers := sliceutil.Filter(names, func(name string) bool {
|
||||
return !sliceutil.Contains(pluckedNames, name)
|
||||
})
|
||||
|
||||
if len(missingPerformers) > 0 {
|
||||
@@ -205,8 +205,8 @@ func (i *Importer) populateTags(ctx context.Context) error {
|
||||
pluckedNames = append(pluckedNames, tag.Name)
|
||||
}
|
||||
|
||||
missingTags := stringslice.StrFilter(names, func(name string) bool {
|
||||
return !stringslice.StrInclude(pluckedNames, name)
|
||||
missingTags := sliceutil.Filter(names, func(name string) bool {
|
||||
return !sliceutil.Contains(pluckedNames, name)
|
||||
})
|
||||
|
||||
if len(missingTags) > 0 {
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/intslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
)
|
||||
|
||||
type ContentsChangedError struct {
|
||||
@@ -42,7 +42,7 @@ func (s *Service) ValidateImageGalleryChange(ctx context.Context, i *models.Imag
|
||||
changedIDs = updateIDs.IDs
|
||||
case models.RelationshipUpdateModeSet:
|
||||
// get the difference between the two lists
|
||||
changedIDs = intslice.IntNotIntersect(i.GalleryIDs.List(), updateIDs.IDs)
|
||||
changedIDs = sliceutil.NotIntersect(i.GalleryIDs.List(), updateIDs.IDs)
|
||||
}
|
||||
|
||||
galleries, err := s.Repository.FindMany(ctx, changedIDs)
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/models/jsonschema"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
)
|
||||
|
||||
type GalleryFinder interface {
|
||||
@@ -229,8 +229,8 @@ func (i *Importer) populatePerformers(ctx context.Context) error {
|
||||
pluckedNames = append(pluckedNames, performer.Name)
|
||||
}
|
||||
|
||||
missingPerformers := stringslice.StrFilter(names, func(name string) bool {
|
||||
return !stringslice.StrInclude(pluckedNames, name)
|
||||
missingPerformers := sliceutil.Filter(names, func(name string) bool {
|
||||
return !sliceutil.Contains(pluckedNames, name)
|
||||
})
|
||||
|
||||
if len(missingPerformers) > 0 {
|
||||
@@ -365,8 +365,8 @@ func importTags(ctx context.Context, tagWriter models.TagFinderCreator, names []
|
||||
pluckedNames = append(pluckedNames, tag.Name)
|
||||
}
|
||||
|
||||
missingTags := stringslice.StrFilter(names, func(name string) bool {
|
||||
return !stringslice.StrInclude(pluckedNames, name)
|
||||
missingTags := sliceutil.Filter(names, func(name string) bool {
|
||||
return !sliceutil.Contains(pluckedNames, name)
|
||||
})
|
||||
|
||||
if len(missingTags) > 0 {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/models/paths"
|
||||
"github.com/stashapp/stash/pkg/plugin"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/intslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
"github.com/stashapp/stash/pkg/txn"
|
||||
)
|
||||
|
||||
@@ -355,7 +355,7 @@ func (h *ScanHandler) getGalleryToAssociate(ctx context.Context, newImage *model
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if g != nil && !intslice.IntInclude(newImage.GalleryIDs.List(), g.ID) {
|
||||
if g != nil && !sliceutil.Contains(newImage.GalleryIDs.List(), g.ID) {
|
||||
return g, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/stashapp/stash/pkg/image"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/scene"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -67,7 +67,7 @@ func getPathWords(path string, trimExt bool) []string {
|
||||
// just use the first two characters
|
||||
// #2293 - need to convert to unicode runes for the substring, otherwise
|
||||
// the resulting string is corrupted.
|
||||
ret = stringslice.StrAppendUnique(ret, string([]rune(w)[0:2]))
|
||||
ret = sliceutil.AppendUnique(ret, string([]rune(w)[0:2]))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,12 +78,12 @@ func (u *UpdateIDs) ImpactedIDs(existing []int) []int {
|
||||
|
||||
switch u.Mode {
|
||||
case RelationshipUpdateModeAdd:
|
||||
return intslice.IntExclude(u.IDs, existing)
|
||||
return sliceutil.Exclude(u.IDs, existing)
|
||||
case RelationshipUpdateModeRemove:
|
||||
return intslice.IntIntercect(existing, u.IDs)
|
||||
return sliceutil.Intersect(existing, u.IDs)
|
||||
case RelationshipUpdateModeSet:
|
||||
// get the difference between the two lists
|
||||
return intslice.IntNotIntersect(existing, u.IDs)
|
||||
return sliceutil.NotIntersect(existing, u.IDs)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/models/jsonschema"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
)
|
||||
|
||||
@@ -74,8 +74,8 @@ func importTags(ctx context.Context, tagWriter models.TagFinderCreator, names []
|
||||
pluckedNames = append(pluckedNames, tag.Name)
|
||||
}
|
||||
|
||||
missingTags := stringslice.StrFilter(names, func(name string) bool {
|
||||
return !stringslice.StrInclude(pluckedNames, name)
|
||||
missingTags := sliceutil.Filter(names, func(name string) bool {
|
||||
return !sliceutil.Contains(pluckedNames, name)
|
||||
})
|
||||
|
||||
if len(missingTags) > 0 {
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/plugin/common"
|
||||
"github.com/stashapp/stash/pkg/session"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
"github.com/stashapp/stash/pkg/txn"
|
||||
)
|
||||
|
||||
@@ -140,7 +140,7 @@ func (c Cache) enabledPlugins() []Config {
|
||||
|
||||
var ret []Config
|
||||
for _, p := range c.plugins {
|
||||
disabled := stringslice.StrInclude(disabledPlugins, p.id)
|
||||
disabled := sliceutil.Contains(disabledPlugins, p.id)
|
||||
|
||||
if !disabled {
|
||||
ret = append(ret, p)
|
||||
@@ -153,7 +153,7 @@ func (c Cache) enabledPlugins() []Config {
|
||||
func (c Cache) pluginDisabled(id string) bool {
|
||||
disabledPlugins := c.config.GetDisabledPlugins()
|
||||
|
||||
return stringslice.StrInclude(disabledPlugins, id)
|
||||
return sliceutil.Contains(disabledPlugins, id)
|
||||
}
|
||||
|
||||
// ListPlugins returns plugin details for all of the loaded plugins.
|
||||
@@ -164,7 +164,7 @@ func (c Cache) ListPlugins() []*Plugin {
|
||||
for _, s := range c.plugins {
|
||||
p := s.toPlugin()
|
||||
|
||||
disabled := stringslice.StrInclude(disabledPlugins, p.ID)
|
||||
disabled := sliceutil.Contains(disabledPlugins, p.ID)
|
||||
p.Enabled = !disabled
|
||||
|
||||
ret = append(ret, p)
|
||||
@@ -276,7 +276,7 @@ func (c Cache) executePostHooks(ctx context.Context, hookType HookTriggerEnum, h
|
||||
hooks := p.getHooks(hookType)
|
||||
// don't revisit a plugin we've already visited
|
||||
// only log if there's hooks that we're skipping
|
||||
if len(hooks) > 0 && stringslice.StrInclude(visitedPlugins, p.id) {
|
||||
if len(hooks) > 0 && sliceutil.Contains(visitedPlugins, p.id) {
|
||||
logger.Debugf("plugin ID '%s' already triggered, not re-triggering", p.id)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/models/json"
|
||||
"github.com/stashapp/stash/pkg/models/jsonschema"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/intslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
)
|
||||
|
||||
@@ -125,7 +125,7 @@ func GetDependentTagIDs(ctx context.Context, tags TagFinder, markerReader models
|
||||
}
|
||||
|
||||
for _, tt := range t {
|
||||
ret = intslice.IntAppendUnique(ret, tt.ID)
|
||||
ret = sliceutil.AppendUnique(ret, tt.ID)
|
||||
}
|
||||
|
||||
sm, err := markerReader.FindBySceneID(ctx, scene.ID)
|
||||
@@ -134,14 +134,14 @@ func GetDependentTagIDs(ctx context.Context, tags TagFinder, markerReader models
|
||||
}
|
||||
|
||||
for _, smm := range sm {
|
||||
ret = intslice.IntAppendUnique(ret, smm.PrimaryTagID)
|
||||
ret = sliceutil.AppendUnique(ret, smm.PrimaryTagID)
|
||||
smmt, err := tags.FindBySceneMarkerID(ctx, smm.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid tags for scene marker: %v", err)
|
||||
}
|
||||
|
||||
for _, smmtt := range smmt {
|
||||
ret = intslice.IntAppendUnique(ret, smmtt.ID)
|
||||
ret = sliceutil.AppendUnique(ret, smmtt.ID)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/models/jsonschema"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
)
|
||||
|
||||
@@ -246,8 +246,8 @@ func (i *Importer) populatePerformers(ctx context.Context) error {
|
||||
pluckedNames = append(pluckedNames, performer.Name)
|
||||
}
|
||||
|
||||
missingPerformers := stringslice.StrFilter(names, func(name string) bool {
|
||||
return !stringslice.StrInclude(pluckedNames, name)
|
||||
missingPerformers := sliceutil.Filter(names, func(name string) bool {
|
||||
return !sliceutil.Contains(pluckedNames, name)
|
||||
})
|
||||
|
||||
if len(missingPerformers) > 0 {
|
||||
@@ -442,8 +442,8 @@ func importTags(ctx context.Context, tagWriter models.TagFinderCreator, names []
|
||||
pluckedNames = append(pluckedNames, tag.Name)
|
||||
}
|
||||
|
||||
missingTags := stringslice.StrFilter(names, func(name string) bool {
|
||||
return !stringslice.StrInclude(pluckedNames, name)
|
||||
missingTags := sliceutil.Filter(names, func(name string) bool {
|
||||
return !sliceutil.Contains(pluckedNames, name)
|
||||
})
|
||||
|
||||
if len(missingTags) > 0 {
|
||||
|
||||
@@ -9,16 +9,16 @@ import (
|
||||
"github.com/stashapp/stash/pkg/fsutil"
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/intslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
"github.com/stashapp/stash/pkg/txn"
|
||||
)
|
||||
|
||||
func (s *Service) Merge(ctx context.Context, sourceIDs []int, destinationID int, scenePartial models.ScenePartial) error {
|
||||
// ensure source ids are unique
|
||||
sourceIDs = intslice.IntAppendUniques(nil, sourceIDs)
|
||||
sourceIDs = sliceutil.AppendUniques(nil, sourceIDs)
|
||||
|
||||
// ensure destination is not in source list
|
||||
if intslice.IntInclude(sourceIDs, destinationID) {
|
||||
if sliceutil.Contains(sourceIDs, destinationID) {
|
||||
return errors.New("destination scene cannot be in source list")
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
)
|
||||
|
||||
type mappedQuery interface {
|
||||
@@ -730,8 +730,8 @@ func (c mappedScraperAttrConfig) concatenateResults(nodes []string) string {
|
||||
}
|
||||
|
||||
func (c mappedScraperAttrConfig) cleanResults(nodes []string) []string {
|
||||
cleaned := stringslice.StrUnique(nodes) // remove duplicate values
|
||||
cleaned = stringslice.StrDelete(cleaned, "") // remove empty values
|
||||
cleaned := sliceutil.Unique(nodes) // remove duplicate values
|
||||
cleaned = sliceutil.Delete(cleaned, "") // remove empty values
|
||||
return cleaned
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/gorilla/securecookie"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
)
|
||||
|
||||
type key int
|
||||
@@ -179,7 +179,7 @@ func GetVisitedPlugins(ctx context.Context) []string {
|
||||
|
||||
func AddVisitedPlugin(ctx context.Context, pluginID string) context.Context {
|
||||
curVal := GetVisitedPlugins(ctx)
|
||||
curVal = stringslice.StrAppendUnique(curVal, pluginID)
|
||||
curVal = sliceutil.AppendUnique(curVal, pluginID)
|
||||
return setVisitedPlugins(ctx, curVal)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
package sliceutil
|
||||
|
||||
import "reflect"
|
||||
|
||||
// Exclude removes all instances of any value in toExclude from the vs
|
||||
// slice. It returns the new or unchanged slice.
|
||||
func Exclude[T comparable](vs []T, toExclude []T) []T {
|
||||
var ret []T
|
||||
for _, v := range vs {
|
||||
if !Include(toExclude, v) {
|
||||
ret = append(ret, v)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@@ -24,23 +11,24 @@ func Index[T comparable](vs []T, t T) int {
|
||||
return -1
|
||||
}
|
||||
|
||||
func Include[T comparable](vs []T, t T) bool {
|
||||
// Contains returns whether the vs slice contains t.
|
||||
func Contains[T comparable](vs []T, t T) bool {
|
||||
return Index(vs, t) >= 0
|
||||
}
|
||||
|
||||
// IntAppendUnique appends toAdd to the vs int slice if toAdd does not already
|
||||
// exist in the slice. It returns the new or unchanged int slice.
|
||||
// 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 Include(vs, toAdd) {
|
||||
if Contains(vs, toAdd) {
|
||||
return vs
|
||||
}
|
||||
|
||||
return append(vs, toAdd)
|
||||
}
|
||||
|
||||
// IntAppendUniques appends a slice of values to the vs slice. It only
|
||||
// appends values that do not already exist in the slice. It returns the new or
|
||||
// unchanged slice.
|
||||
// AppendUniques appends a slice of values to the vs slice. It only
|
||||
// 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 {
|
||||
for _, v := range toAdd {
|
||||
vs = AppendUnique(vs, v)
|
||||
@@ -49,51 +37,90 @@ func AppendUniques[T comparable](vs []T, toAdd []T) []T {
|
||||
return vs
|
||||
}
|
||||
|
||||
// SliceSame returns true if the two provided lists have the same elements,
|
||||
// regardless of order. Panics if either parameter is not a slice.
|
||||
func SliceSame(a, b interface{}) bool {
|
||||
v1 := reflect.ValueOf(a)
|
||||
v2 := reflect.ValueOf(b)
|
||||
|
||||
if (v1.IsValid() && v1.Kind() != reflect.Slice) || (v2.IsValid() && v2.Kind() != reflect.Slice) {
|
||||
panic("not a slice")
|
||||
// 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
|
||||
for _, v := range vs {
|
||||
if !Contains(toExclude, v) {
|
||||
ret = append(ret, v)
|
||||
}
|
||||
}
|
||||
|
||||
v1Len := 0
|
||||
v2Len := 0
|
||||
return ret
|
||||
}
|
||||
|
||||
v1Valid := v1.IsValid()
|
||||
v2Valid := v2.IsValid()
|
||||
|
||||
if v1Valid {
|
||||
v1Len = v1.Len()
|
||||
// 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
|
||||
for _, v := range vs {
|
||||
if _, exists := distinctValues[v]; !exists {
|
||||
distinctValues[v] = struct{}{}
|
||||
ret = append(ret, v)
|
||||
}
|
||||
}
|
||||
if v2Valid {
|
||||
v2Len = v2.Len()
|
||||
return ret
|
||||
}
|
||||
|
||||
// Delete returns a copy of the vs slice with toDel values removed.
|
||||
func Delete[T comparable](vs []T, toDel T) []T {
|
||||
var ret []T
|
||||
for _, v := range vs {
|
||||
if v != toDel {
|
||||
ret = append(ret, v)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Intersect returns a slice containing values that exist in both provided slices.
|
||||
func Intersect[T comparable](a []T, b []T) []T {
|
||||
var ret []T
|
||||
for _, v := range a {
|
||||
if Contains(b, v) {
|
||||
ret = append(ret, v)
|
||||
}
|
||||
}
|
||||
|
||||
if !v1Valid || !v2Valid {
|
||||
return v1Len == v2Len
|
||||
return ret
|
||||
}
|
||||
|
||||
// NotIntersect returns a slice containing values that do not exist in both provided slices.
|
||||
func NotIntersect[T comparable](a []T, b []T) []T {
|
||||
var ret []T
|
||||
for _, v := range a {
|
||||
if !Contains(b, v) {
|
||||
ret = append(ret, v)
|
||||
}
|
||||
}
|
||||
|
||||
if v1Len != v2Len {
|
||||
for _, v := range b {
|
||||
if !Contains(a, v) {
|
||||
ret = append(ret, v)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// SliceSame returns true if the two provided slices have equal elements,
|
||||
// regardless of order.
|
||||
func SliceSame[T comparable](a []T, b []T) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
if v1.Type() != v2.Type() {
|
||||
return false
|
||||
}
|
||||
|
||||
visited := make(map[int]bool)
|
||||
for i := 0; i < v1.Len(); i++ {
|
||||
visited := make(map[int]struct{})
|
||||
for i := range a {
|
||||
found := false
|
||||
for j := 0; j < v2.Len(); j++ {
|
||||
if visited[j] {
|
||||
for j := range b {
|
||||
if _, exists := visited[j]; exists {
|
||||
continue
|
||||
}
|
||||
if reflect.DeepEqual(v1.Index(i).Interface(), v2.Index(j).Interface()) {
|
||||
if a[i] == b[j] {
|
||||
found = true
|
||||
visited[j] = true
|
||||
visited[j] = struct{}{}
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -105,3 +132,24 @@ func SliceSame(a, b interface{}) bool {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Filter returns a slice containing the elements of the vs slice
|
||||
// that meet the condition specified by f.
|
||||
func Filter[T any](vs []T, f func(T) bool) []T {
|
||||
var ret []T
|
||||
for _, v := range vs {
|
||||
if f(v) {
|
||||
ret = append(ret, v)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Filter returns the result of applying f to each element of the vs slice.
|
||||
func Map[T any, V any](vs []T, f func(T) V) []V {
|
||||
ret := make([]V, len(vs))
|
||||
for i, v := range vs {
|
||||
ret[i] = f(v)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
@@ -1,32 +1,21 @@
|
||||
package sliceutil
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSliceSame(t *testing.T) {
|
||||
objs := []struct {
|
||||
a string
|
||||
b int
|
||||
}{
|
||||
{"1", 2},
|
||||
{"1", 2},
|
||||
{"2", 1},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
a interface{}
|
||||
b interface{}
|
||||
a []int
|
||||
b []int
|
||||
want bool
|
||||
}{
|
||||
{"nil values", nil, nil, true},
|
||||
{"empty", []int{}, []int{}, true},
|
||||
{"nil and empty", nil, []int{}, true},
|
||||
{
|
||||
"different type",
|
||||
[]string{"1"},
|
||||
[]int{1},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"different length",
|
||||
[]int{1, 2, 3},
|
||||
@@ -69,24 +58,11 @@ func TestSliceSame(t *testing.T) {
|
||||
[]int{1, 1, 2, 2, 3},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"structs equal",
|
||||
objs[0:1],
|
||||
objs[0:1],
|
||||
true,
|
||||
},
|
||||
{
|
||||
"structs not equal",
|
||||
objs[0:2],
|
||||
objs[1:3],
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := SliceSame(tt.a, tt.b); got != tt.want {
|
||||
t.Errorf("SliceSame() = %v, want %v", got, tt.want)
|
||||
}
|
||||
got := SliceSame(tt.a, tt.b)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,87 +2,6 @@ package intslice
|
||||
|
||||
import "strconv"
|
||||
|
||||
// IntIndex returns the first index of the provided int value in the provided
|
||||
// int slice. It returns -1 if it is not found.
|
||||
func IntIndex(vs []int, t int) int {
|
||||
for i, v := range vs {
|
||||
if v == t {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// IntInclude returns true if the provided int value exists in the provided int
|
||||
// slice.
|
||||
func IntInclude(vs []int, t int) bool {
|
||||
return IntIndex(vs, t) >= 0
|
||||
}
|
||||
|
||||
// IntAppendUnique appends toAdd to the vs int slice if toAdd does not already
|
||||
// exist in the slice. It returns the new or unchanged int slice.
|
||||
func IntAppendUnique(vs []int, toAdd int) []int {
|
||||
if IntInclude(vs, toAdd) {
|
||||
return vs
|
||||
}
|
||||
|
||||
return append(vs, toAdd)
|
||||
}
|
||||
|
||||
// IntAppendUniques appends a slice of int values to the vs int slice. It only
|
||||
// appends values that do not already exist in the slice. It returns the new or
|
||||
// unchanged int slice.
|
||||
func IntAppendUniques(vs []int, toAdd []int) []int {
|
||||
for _, v := range toAdd {
|
||||
vs = IntAppendUnique(vs, v)
|
||||
}
|
||||
|
||||
return vs
|
||||
}
|
||||
|
||||
// IntExclude removes all instances of any value in toExclude from the vs int
|
||||
// slice. It returns the new or unchanged int slice.
|
||||
func IntExclude(vs []int, toExclude []int) []int {
|
||||
var ret []int
|
||||
for _, v := range vs {
|
||||
if !IntInclude(toExclude, v) {
|
||||
ret = append(ret, v)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// IntIntercect returns a slice of ints containing values that exist in both provided slices.
|
||||
func IntIntercect(v1, v2 []int) []int {
|
||||
var ret []int
|
||||
for _, v := range v1 {
|
||||
if IntInclude(v2, v) {
|
||||
ret = append(ret, v)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// IntNotIntersect returns a slice of ints containing values that do not exist in both provided slices.
|
||||
func IntNotIntersect(v1, v2 []int) []int {
|
||||
var ret []int
|
||||
for _, v := range v1 {
|
||||
if !IntInclude(v2, v) {
|
||||
ret = append(ret, v)
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range v2 {
|
||||
if !IntInclude(v1, v) {
|
||||
ret = append(ret, v)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// IntSliceToStringSlice converts a slice of ints to a slice of strings.
|
||||
func IntSliceToStringSlice(ss []int) []string {
|
||||
ret := make([]string, len(ss))
|
||||
|
||||
@@ -5,97 +5,6 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// https://gobyexample.com/collection-functions
|
||||
|
||||
func StrIndex(vs []string, t string) int {
|
||||
for i, v := range vs {
|
||||
if v == t {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func StrInclude(vs []string, t string) bool {
|
||||
return StrIndex(vs, t) >= 0
|
||||
}
|
||||
|
||||
func StrFilter(vs []string, f func(string) bool) []string {
|
||||
vsf := make([]string, 0)
|
||||
for _, v := range vs {
|
||||
if f(v) {
|
||||
vsf = append(vsf, v)
|
||||
}
|
||||
}
|
||||
return vsf
|
||||
}
|
||||
|
||||
func StrMap(vs []string, f func(string) string) []string {
|
||||
vsm := make([]string, len(vs))
|
||||
for i, v := range vs {
|
||||
vsm[i] = f(v)
|
||||
}
|
||||
return vsm
|
||||
}
|
||||
|
||||
// StrAppendUnique appends toAdd to the vs string slice if toAdd does not already
|
||||
// exist in the slice. It returns the new or unchanged string slice.
|
||||
func StrAppendUnique(vs []string, toAdd string) []string {
|
||||
if StrInclude(vs, toAdd) {
|
||||
return vs
|
||||
}
|
||||
|
||||
return append(vs, toAdd)
|
||||
}
|
||||
|
||||
// StrAppendUniques appends a slice of string values to the vs string slice. It only
|
||||
// appends values that do not already exist in the slice. It returns the new or
|
||||
// unchanged string slice.
|
||||
func StrAppendUniques(vs []string, toAdd []string) []string {
|
||||
for _, v := range toAdd {
|
||||
vs = StrAppendUnique(vs, v)
|
||||
}
|
||||
|
||||
return vs
|
||||
}
|
||||
|
||||
// StrExclude removes all instances of any value in toExclude from the vs string
|
||||
// slice. It returns the new or unchanged string slice.
|
||||
func StrExclude(vs []string, toExclude []string) []string {
|
||||
var ret []string
|
||||
for _, v := range vs {
|
||||
if !StrInclude(toExclude, v) {
|
||||
ret = append(ret, v)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// StrUnique returns the vs string slice with non-unique values removed.
|
||||
func StrUnique(vs []string) []string {
|
||||
distinctValues := make(map[string]struct{})
|
||||
var ret []string
|
||||
for _, v := range vs {
|
||||
if _, exists := distinctValues[v]; !exists {
|
||||
distinctValues[v] = struct{}{}
|
||||
ret = append(ret, v)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// StrDelete returns the vs string slice with toDel values removed.
|
||||
func StrDelete(vs []string, toDel string) []string {
|
||||
var ret []string
|
||||
for _, v := range vs {
|
||||
if v != toDel {
|
||||
ret = append(ret, v)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// StringSliceToIntSlice converts a slice of strings to a slice of ints.
|
||||
// Returns an error if any values cannot be parsed.
|
||||
func StringSliceToIntSlice(ss []string) ([]int, error) {
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/doug-martin/goqu/v9/exp"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/intslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
"gopkg.in/guregu/null.v4"
|
||||
"gopkg.in/guregu/null.v4/zero"
|
||||
)
|
||||
@@ -343,7 +343,7 @@ func (qb *GalleryStore) FindMany(ctx context.Context, ids []int) ([]*models.Gall
|
||||
}
|
||||
|
||||
for _, s := range unsorted {
|
||||
i := intslice.IntIndex(ids, s.ID)
|
||||
i := sliceutil.Index(ids, s.ID)
|
||||
galleries[i] = s
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/intslice"
|
||||
"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 := intslice.IntIndex(ids, s.ID)
|
||||
i := sliceutil.Index(ids, s.ID)
|
||||
ret[i] = s
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/intslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
"gopkg.in/guregu/null.v4"
|
||||
"gopkg.in/guregu/null.v4/zero"
|
||||
|
||||
@@ -323,7 +323,7 @@ func (qb *ImageStore) FindMany(ctx context.Context, ids []int) ([]*models.Image,
|
||||
}
|
||||
|
||||
for _, s := range unsorted {
|
||||
i := intslice.IntIndex(ids, s.ID)
|
||||
i := sliceutil.Index(ids, s.ID)
|
||||
images[i] = s
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
"github.com/stashapp/stash/pkg/sqlite"
|
||||
)
|
||||
|
||||
@@ -136,7 +136,7 @@ func (m *schema42Migrator) migratePerformerAliases(id int, aliases string) error
|
||||
}
|
||||
|
||||
// remove duplicates
|
||||
aliasList = stringslice.StrAppendUniques(nil, aliasList)
|
||||
aliasList = sliceutil.AppendUniques(nil, aliasList)
|
||||
|
||||
// insert aliases into table
|
||||
for _, alias := range aliasList {
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"gopkg.in/guregu/null.v4/zero"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/intslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -204,7 +204,7 @@ func (qb *MovieStore) FindMany(ctx context.Context, ids []int) ([]*models.Movie,
|
||||
}
|
||||
|
||||
for _, s := range unsorted {
|
||||
i := intslice.IntIndex(ids, s.ID)
|
||||
i := sliceutil.Index(ids, s.ID)
|
||||
ret[i] = s
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/doug-martin/goqu/v9/exp"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/intslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
"gopkg.in/guregu/null.v4"
|
||||
"gopkg.in/guregu/null.v4/zero"
|
||||
@@ -336,7 +336,7 @@ func (qb *PerformerStore) FindMany(ctx context.Context, ids []int) ([]*models.Pe
|
||||
}
|
||||
|
||||
for _, s := range unsorted {
|
||||
i := intslice.IntIndex(ids, s.ID)
|
||||
i := sliceutil.Index(ids, s.ID)
|
||||
ret[i] = s
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/intslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -182,7 +182,7 @@ func (qb *SavedFilterStore) FindMany(ctx context.Context, ids []int, ignoreNotFo
|
||||
}
|
||||
|
||||
for _, s := range unsorted {
|
||||
i := intslice.IntIndex(ids, s.ID)
|
||||
i := sliceutil.Index(ids, s.ID)
|
||||
ret[i] = s
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
"gopkg.in/guregu/null.v4/zero"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/intslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
)
|
||||
|
||||
@@ -456,7 +456,7 @@ func (qb *SceneStore) FindMany(ctx context.Context, ids []int) ([]*models.Scene,
|
||||
}
|
||||
|
||||
for _, s := range unsorted {
|
||||
i := intslice.IntIndex(ids, s.ID)
|
||||
i := sliceutil.Index(ids, s.ID)
|
||||
scenes[i] = s
|
||||
}
|
||||
|
||||
@@ -1819,7 +1819,7 @@ func (qb *SceneStore) FindDuplicates(ctx context.Context, distance int, duration
|
||||
var sceneIds []int
|
||||
for _, strId := range strIds {
|
||||
if intId, err := strconv.Atoi(strId); err == nil {
|
||||
sceneIds = intslice.IntAppendUnique(sceneIds, intId)
|
||||
sceneIds = sliceutil.AppendUnique(sceneIds, intId)
|
||||
}
|
||||
}
|
||||
// filter out
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/intslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
)
|
||||
|
||||
const sceneMarkerTable = "scene_markers"
|
||||
@@ -171,7 +171,7 @@ func (qb *SceneMarkerStore) FindMany(ctx context.Context, ids []int) ([]*models.
|
||||
}
|
||||
|
||||
for _, s := range unsorted {
|
||||
i := intslice.IntIndex(ids, s.ID)
|
||||
i := sliceutil.Index(ids, s.ID)
|
||||
ret[i] = s
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/intslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -112,7 +112,7 @@ func verifyIDs(t *testing.T, modifier models.CriterionModifier, values []int, re
|
||||
case models.CriterionModifierNotEquals:
|
||||
foundAll := true
|
||||
for _, v := range values {
|
||||
if !intslice.IntInclude(results, v) {
|
||||
if !sliceutil.Contains(results, v) {
|
||||
foundAll = false
|
||||
break
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/intslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -2376,12 +2376,12 @@ func TestSceneQueryPath(t *testing.T) {
|
||||
mustInclude := indexesToIDs(sceneIDs, tt.mustInclude)
|
||||
mustExclude := indexesToIDs(sceneIDs, tt.mustExclude)
|
||||
|
||||
missing := intslice.IntExclude(mustInclude, got.IDs)
|
||||
missing := sliceutil.Exclude(mustInclude, got.IDs)
|
||||
if len(missing) > 0 {
|
||||
t.Errorf("SceneStore.TestSceneQueryPath() missing expected IDs: %v", missing)
|
||||
}
|
||||
|
||||
notExcluded := intslice.IntIntercect(mustExclude, got.IDs)
|
||||
notExcluded := sliceutil.Intersect(mustExclude, got.IDs)
|
||||
if len(notExcluded) > 0 {
|
||||
t.Errorf("SceneStore.TestSceneQueryPath() expected IDs to be excluded: %v", notExcluded)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/intslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
"github.com/stashapp/stash/pkg/sqlite"
|
||||
"github.com/stashapp/stash/pkg/txn"
|
||||
|
||||
@@ -1506,7 +1506,7 @@ func getTagMarkerCount(id int) int {
|
||||
count := 0
|
||||
idx := indexFromID(tagIDs, id)
|
||||
for _, s := range markerSpecs {
|
||||
if s.primaryTagIdx == idx || intslice.IntInclude(s.tagIdxs, idx) {
|
||||
if s.primaryTagIdx == idx || sliceutil.Contains(s.tagIdxs, idx) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"gopkg.in/guregu/null.v4/zero"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/intslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
"github.com/stashapp/stash/pkg/studio"
|
||||
)
|
||||
|
||||
@@ -240,7 +240,7 @@ func (qb *StudioStore) FindMany(ctx context.Context, ids []int) ([]*models.Studi
|
||||
}
|
||||
|
||||
for _, s := range unsorted {
|
||||
i := intslice.IntIndex(ids, s.ID)
|
||||
i := sliceutil.Index(ids, s.ID)
|
||||
ret[i] = s
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,6 @@ import (
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/intslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
||||
)
|
||||
|
||||
type table struct {
|
||||
@@ -202,7 +200,7 @@ func (t *joinTable) insertJoins(ctx context.Context, id int, foreignIDs []int) e
|
||||
defer stmt.Close()
|
||||
|
||||
// eliminate duplicates
|
||||
foreignIDs = intslice.IntAppendUniques(nil, foreignIDs)
|
||||
foreignIDs = sliceutil.AppendUniques(nil, foreignIDs)
|
||||
|
||||
for _, fk := range foreignIDs {
|
||||
if _, err := tx.ExecStmt(ctx, stmt, id, fk); err != nil {
|
||||
@@ -229,7 +227,7 @@ func (t *joinTable) addJoins(ctx context.Context, id int, foreignIDs []int) erro
|
||||
}
|
||||
|
||||
// only add foreign keys that are not already present
|
||||
foreignIDs = intslice.IntExclude(foreignIDs, fks)
|
||||
foreignIDs = sliceutil.Exclude(foreignIDs, fks)
|
||||
return t.insertJoins(ctx, id, foreignIDs)
|
||||
}
|
||||
|
||||
@@ -440,7 +438,7 @@ func (t *stringTable) addJoins(ctx context.Context, id int, v []string) error {
|
||||
}
|
||||
|
||||
// only add values that are not already present
|
||||
filtered := stringslice.StrExclude(v, existing)
|
||||
filtered := sliceutil.Exclude(v, existing)
|
||||
return t.insertJoins(ctx, id, filtered)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"gopkg.in/guregu/null.v4/zero"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/intslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -205,7 +205,7 @@ func (qb *TagStore) FindMany(ctx context.Context, ids []int) ([]*models.Tag, err
|
||||
}
|
||||
|
||||
for _, s := range unsorted {
|
||||
i := intslice.IntIndex(ids, s.ID)
|
||||
i := sliceutil.Index(ids, s.ID)
|
||||
ret[i] = s
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/corona10/goimagehash"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/intslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil"
|
||||
)
|
||||
|
||||
type Phash struct {
|
||||
@@ -58,7 +58,7 @@ func findNeighbors(bucket int, neighbors []int, hashes []*Phash, scenes *[]int)
|
||||
hash := hashes[id]
|
||||
if hash.Bucket == -1 {
|
||||
hash.Bucket = bucket
|
||||
*scenes = intslice.IntAppendUnique(*scenes, hash.SceneID)
|
||||
*scenes = sliceutil.AppendUnique(*scenes, hash.SceneID)
|
||||
findNeighbors(bucket, hash.Neighbors, hashes, scenes)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user