diff --git a/internal/federation/federatingprotocol.go b/internal/federation/federatingprotocol.go index 1a655994c..fdd13f7f0 100644 --- a/internal/federation/federatingprotocol.go +++ b/internal/federation/federatingprotocol.go @@ -162,7 +162,9 @@ func (f *Federator) PostInboxRequestBodyHook(ctx context.Context, r *http.Reques // OtherIRIs will likely contain some // duplicate entries now, so remove them. - otherIRIs = util.UniqueURIs(otherIRIs) + otherIRIs = util.DeduplicateFunc(otherIRIs, + (*url.URL).String, // serialized URL is 'key()' + ) // Finished, set other IRIs on the context // so they can be checked for blocks later. diff --git a/internal/gtsmodel/conversation.go b/internal/gtsmodel/conversation.go index f03f27458..d17cbe6fe 100644 --- a/internal/gtsmodel/conversation.go +++ b/internal/gtsmodel/conversation.go @@ -62,7 +62,7 @@ type Conversation struct { // ConversationOtherAccountsKey creates an OtherAccountsKey from a list of OtherAccountIDs. func ConversationOtherAccountsKey(otherAccountIDs []string) string { - otherAccountIDs = util.UniqueStrings(otherAccountIDs) + otherAccountIDs = util.Deduplicate(otherAccountIDs) slices.Sort(otherAccountIDs) return strings.Join(otherAccountIDs, ",") } diff --git a/internal/media/processingmedia.go b/internal/media/processingmedia.go index b68d8d680..3da036ac7 100644 --- a/internal/media/processingmedia.go +++ b/internal/media/processingmedia.go @@ -218,8 +218,13 @@ func (p *ProcessingMedia) store(ctx context.Context) error { } if width > 0 && height > 0 { - // Determine thumbnail dimensions to use. - thumbWidth, thumbHeight := thumbSize(width, height, aspect, result.rotation) + // Determine thumbnail dimens to use. + thumbWidth, thumbHeight := thumbSize( + width, + height, + aspect, + result.rotation, + ) p.media.FileMeta.Small.Width = thumbWidth p.media.FileMeta.Small.Height = thumbHeight p.media.FileMeta.Small.Size = (thumbWidth * thumbHeight) diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go index c555e1d04..70230cd89 100644 --- a/internal/typeutils/internaltofrontend.go +++ b/internal/typeutils/internaltofrontend.go @@ -856,6 +856,7 @@ func (c *Converter) statusToAPIFilterResults( for _, mention := range s.Mentions { otherAccounts = append(otherAccounts, mention.TargetAccount) } + // If there are no other accounts, skip this check. if len(otherAccounts) > 0 { // Start by assuming that they're all invisible or muted. diff --git a/internal/util/slices.go b/internal/util/slices.go index 0505229e5..955fe8830 100644 --- a/internal/util/slices.go +++ b/internal/util/slices.go @@ -17,7 +17,9 @@ package util -import "slices" +import ( + "slices" +) // Deduplicate deduplicates entries in the given slice. func Deduplicate[T comparable](in []T) []T { @@ -68,9 +70,69 @@ func DeduplicateFunc[T any, C comparable](in []T, key func(v T) C) []T { return deduped } +// Gather will collect the values of type V from input type []T, +// passing each item to 'get' and appending V to the return slice. +func Gather[T, V any](out []V, in []T, get func(T) V) []V { + if get == nil { + panic("nil func") + } + + // Starting write index + // in the resliced / re + // alloc'd output slice. + start := len(out) + + // Total required slice len. + total := start + len(in) + + if total > cap(out) { + // Reallocate output with + // capacity for total len. + out2 := make([]V, len(out), total) + copy(out2, out) + out = out2 + } + + // Reslice with capacity + // up to total required. + out = out[:total] + + // Gather vs from 'in'. + for i, v := range in { + j := start + i + out[j] = get(v) + } + + return out +} + +// GatherIf is functionally similar to Gather(), but only when return bool is true. +// If you don't need to check the boolean, Gather() will be very slightly faster. +func GatherIf[T, V any](out []V, in []T, get func(T) (V, bool)) []V { + if get == nil { + panic("nil func") + } + + if cap(out)-len(out) < len(in) { + // Reallocate output with capacity for 'in'. + out2 := make([]V, len(out), cap(out)+len(in)) + copy(out2, out) + out = out2 + } + + // Gather vs from 'in'. + for _, v := range in { + if v, ok := get(v); ok { + out = append(out, v) + } + } + + return out +} + // Collate will collect the values of type K from input type []T, // passing each item to 'get' and deduplicating the end result. -// Compared to Deduplicate() this returns []K, NOT input type []T. +// This is equivalent to calling Gather() followed by Deduplicate(). func Collate[T any, K comparable](in []T, get func(T) K) []K { if get == nil { panic("nil func") diff --git a/internal/util/slices_test.go b/internal/util/slices_test.go new file mode 100644 index 000000000..c93e489f5 --- /dev/null +++ b/internal/util/slices_test.go @@ -0,0 +1,94 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package util_test + +import ( + "net/url" + "slices" + "testing" + + "github.com/superseriousbusiness/gotosocial/internal/util" +) + +var ( + testURLSlice = []*url.URL{} +) + +func TestGather(t *testing.T) { + out := util.Gather(nil, []*url.URL{ + {Scheme: "https", Host: "google.com", Path: "/some-search"}, + {Scheme: "http", Host: "example.com", Path: "/robots.txt"}, + }, (*url.URL).String) + if !slices.Equal(out, []string{ + "https://google.com/some-search", + "http://example.com/robots.txt", + }) { + t.Fatal("unexpected gather output") + } + + out = util.Gather([]string{ + "starting input string", + "another starting input", + }, []*url.URL{ + {Scheme: "https", Host: "google.com", Path: "/some-search"}, + {Scheme: "http", Host: "example.com", Path: "/robots.txt"}, + }, (*url.URL).String) + if !slices.Equal(out, []string{ + "starting input string", + "another starting input", + "https://google.com/some-search", + "http://example.com/robots.txt", + }) { + t.Fatal("unexpected gather output") + } +} + +func TestGatherIf(t *testing.T) { + out := util.GatherIf(nil, []string{ + "hello world", + "not hello world", + "hello world", + }, func(s string) (string, bool) { + return s, s == "hello world" + }) + if !slices.Equal(out, []string{ + "hello world", + "hello world", + }) { + t.Fatal("unexpected gatherif output") + } + + out = util.GatherIf([]string{ + "starting input string", + "another starting input", + }, []string{ + "hello world", + "not hello world", + "hello world", + }, func(s string) (string, bool) { + return s, s == "hello world" + }) + if !slices.Equal(out, []string{ + "starting input string", + "another starting input", + "hello world", + "hello world", + }) { + t.Fatal("unexpected gatherif output") + } +} diff --git a/internal/util/unique.go b/internal/util/unique.go index f0ded1446..bad553d3f 100644 --- a/internal/util/unique.go +++ b/internal/util/unique.go @@ -17,48 +17,57 @@ package util -import "net/url" +// Set represents a hashmap of only keys, +// useful for deduplication / key checking. +type Set[T comparable] map[T]struct{} -// UniqueStrings returns a deduplicated version of the given -// slice of strings, without changing the order of the entries. -func UniqueStrings(strings []string) []string { - var ( - l = len(strings) - keys = make(map[string]any, l) // Use map to dedupe items. - unique = make([]string, 0, l) // Return slice. - ) - - for _, str := range strings { - // Check if already set as a key in the map; - // if not, add to return slice + mark key as set. - if _, set := keys[str]; !set { - keys[str] = nil // Value doesn't matter. - unique = append(unique, str) - } +// ToSet creates a Set[T] from given values, +// noting that this does not maintain any order. +func ToSet[T comparable](in []T) Set[T] { + set := make(Set[T], len(in)) + for _, v := range in { + set[v] = struct{}{} } - - return unique + return set } -// UniqueURIs returns a deduplicated version of the given -// slice of URIs, without changing the order of the entries. -func UniqueURIs(uris []*url.URL) []*url.URL { - var ( - l = len(uris) - keys = make(map[string]any, l) // Use map to dedupe items. - unique = make([]*url.URL, 0, l) // Return slice. - ) +// FromSet extracts the values from set to slice, +// noting that this does not maintain any order. +func FromSet[T comparable](in Set[T]) []T { + out := make([]T, len(in)) + var i int + for v := range in { + out[i] = v + i++ + } + return out +} - for _, uri := range uris { - uriStr := uri.String() - - // Check if already set as a key in the map; - // if not, add to return slice + mark key as set. - if _, set := keys[uriStr]; !set { - keys[uriStr] = nil // Value doesn't matter. - unique = append(unique, uri) +// In returns input slice filtered to +// only contain those in receiving set. +func (s Set[T]) In(vs []T) []T { + out := make([]T, 0, len(vs)) + for _, v := range vs { + if _, ok := s[v]; ok { + out = append(out, v) } } - - return unique + return out +} + +// NotIn is the functional inverse of In(). +func (s Set[T]) NotIn(vs []T) []T { + out := make([]T, 0, len(vs)) + for _, v := range vs { + if _, ok := s[v]; !ok { + out = append(out, v) + } + } + return out +} + +// Has returns if value is in Set. +func (s Set[T]) Has(v T) bool { + _, ok := s[v] + return ok }