diff --git a/cmd/photoprism/photoprism.go b/cmd/photoprism/photoprism.go index 67cdface4..9abbdcb0a 100644 --- a/cmd/photoprism/photoprism.go +++ b/cmd/photoprism/photoprism.go @@ -44,7 +44,7 @@ const appDescription = "PhotoPrism® is an AI-Powered Photos App for the Decentr const appCopyright = "(c) 2018-2025 PhotoPrism UG. All rights reserved." // Metadata contains build specific information. -var Metadata = map[string]interface{}{ +var Metadata = map[string]any{ "Name": appName, "About": appAbout, "Edition": appEdition, diff --git a/internal/ai/face/embeddings.go b/internal/ai/face/embeddings.go index 5533496b7..e54d186ac 100644 --- a/internal/ai/face/embeddings.go +++ b/internal/ai/face/embeddings.go @@ -157,12 +157,12 @@ func EmbeddingsMidpoint(embeddings Embeddings) (result Embedding, radius float64 normalizeEmbedding(emb) - for j := 0; j < dim; j++ { + for j := range dim { result[j] += emb[j] } } - for i := 0; i < dim; i++ { + for i := range dim { result[i] *= invCount } @@ -172,7 +172,7 @@ func EmbeddingsMidpoint(embeddings Embeddings) (result Embedding, radius float64 for _, emb := range embeddings { var dist float64 - for i := 0; i < dim; i++ { + for i := range dim { diff := result[i] - emb[i] dist += diff * diff } diff --git a/internal/ai/face/engine_onnx.go b/internal/ai/face/engine_onnx.go index 1bc083dc3..8c9508ad0 100644 --- a/internal/ai/face/engine_onnx.go +++ b/internal/ai/face/engine_onnx.go @@ -174,10 +174,7 @@ func NewONNXEngine(opts ONNXOptions) (DetectionEngine, error) { threads := opts.Threads if threads == 0 { - threads = runtime.NumCPU() / 2 - if threads < 1 { - threads = 1 - } + threads = max(runtime.NumCPU()/2, 1) } if err := sessionOpts.SetIntraOpNumThreads(threads); err != nil { @@ -560,11 +557,11 @@ func (o *onnxEngine) anchorCenters(height, width, stride, anchors int) []float32 centers := make([]float32, height*width*anchors*2) idx := 0 - for y := 0; y < height; y++ { + for y := range height { cy := float32(y * stride) - for x := 0; x < width; x++ { + for x := range width { cx := float32(x * stride) - for a := 0; a < anchors; a++ { + for range anchors { centers[idx] = cx centers[idx+1] = cy idx += 2 @@ -599,7 +596,7 @@ func nonMaxSuppression(boxes []onnxDetection, threshold float32) []onnxDetection picked := make([]onnxDetection, 0, len(boxes)) suppressed := make([]bool, len(boxes)) - for i := 0; i < len(boxes); i++ { + for i := range boxes { if suppressed[i] { continue } diff --git a/internal/ai/face/face.go b/internal/ai/face/face.go index 6be4b5e1c..c9001435d 100644 --- a/internal/ai/face/face.go +++ b/internal/ai/face/face.go @@ -38,7 +38,7 @@ type Face struct { Rows int `json:"rows,omitempty"` Cols int `json:"cols,omitempty"` Score int `json:"score,omitempty"` - Area Area `json:"face,omitempty"` + Area Area `json:"face"` Eyes Areas `json:"eyes,omitempty"` Landmarks Areas `json:"landmarks,omitempty"` Embeddings Embeddings `json:"embeddings,omitempty"` diff --git a/internal/ai/face/model.go b/internal/ai/face/model.go index adfbfa7d7..dc355af82 100644 --- a/internal/ai/face/model.go +++ b/internal/ai/face/model.go @@ -187,12 +187,12 @@ func imageToTensor(img image.Image, resolution int) (tfTensor *tf.Tensor, err er var tfImage [1][][][3]float32 - for j := 0; j < resolution; j++ { + for range resolution { tfImage[0] = append(tfImage[0], make([][3]float32, resolution)) } - for i := 0; i < resolution; i++ { - for j := 0; j < resolution; j++ { + for i := range resolution { + for j := range resolution { r, g, b, _ := img.At(i, j).RGBA() tfImage[0][j][i][0] = convertValue(r) tfImage[0][j][i][1] = convertValue(g) diff --git a/internal/ai/face/model_test.go b/internal/ai/face/model_test.go index 069781503..608273ac0 100644 --- a/internal/ai/face/model_test.go +++ b/internal/ai/face/model_test.go @@ -112,8 +112,8 @@ func TestNet(t *testing.T) { // Dist Matrix correct := 0 - for i := 0; i < len(embeddings); i++ { - for j := 0; j < len(embeddings); j++ { + for i := range embeddings { + for j := range embeddings { if i >= j { continue } diff --git a/internal/ai/tensorflow/info.go b/internal/ai/tensorflow/info.go index 581e45875..b10a5a3b0 100644 --- a/internal/ai/tensorflow/info.go +++ b/internal/ai/tensorflow/info.go @@ -121,7 +121,7 @@ func (o ResizeOperation) MarshalYAML() (any, error) { } // UnmarshalYAML decodes the resize operation from YAML input. -func (o *ResizeOperation) UnmarshalYAML(unmarshal func(interface{}) error) error { +func (o *ResizeOperation) UnmarshalYAML(unmarshal func(any) error) error { var s string if err := unmarshal(&s); err != nil { return err @@ -269,7 +269,7 @@ func (o ColorChannelOrder) MarshalYAML() (any, error) { } // UnmarshalYAML decodes the channel order from YAML input. -func (o *ColorChannelOrder) UnmarshalYAML(unmarshal func(interface{}) error) error { +func (o *ColorChannelOrder) UnmarshalYAML(unmarshal func(any) error) error { var s string if err := unmarshal(&s); err != nil { return err diff --git a/internal/ai/vision/api_response.go b/internal/ai/vision/api_response.go index 364f56e7d..9754b8ece 100644 --- a/internal/ai/vision/api_response.go +++ b/internal/ai/vision/api_response.go @@ -19,7 +19,7 @@ type ApiResponse struct { Code int `yaml:"Code,omitempty" json:"code,omitempty"` Error string `yaml:"Error,omitempty" json:"error,omitempty"` Model *Model `yaml:"Model,omitempty" json:"model,omitempty"` - Result ApiResult `yaml:"Result,omitempty" json:"result,omitempty"` + Result ApiResult `yaml:"Result,omitempty" json:"result"` } // Err returns an error if the request has failed. diff --git a/internal/ai/vision/model.go b/internal/ai/vision/model.go index a4b74fc69..a374ea70f 100644 --- a/internal/ai/vision/model.go +++ b/internal/ai/vision/model.go @@ -47,7 +47,7 @@ type Model struct { Resolution int `yaml:"Resolution,omitempty" json:"resolution,omitempty"` TensorFlow *tensorflow.ModelInfo `yaml:"TensorFlow,omitempty" json:"tensorflow,omitempty"` Options *ModelOptions `yaml:"Options,omitempty" json:"options,omitempty"` - Service Service `yaml:"Service,omitempty" json:"service,omitempty"` + Service Service `yaml:"Service,omitempty" json:"service"` Path string `yaml:"Path,omitempty" json:"-"` Disabled bool `yaml:"Disabled,omitempty" json:"disabled,omitempty"` classifyModel *classify.Model diff --git a/internal/ai/vision/model_filters_test.go b/internal/ai/vision/model_filters_test.go index ae7f335b1..3565371e9 100644 --- a/internal/ai/vision/model_filters_test.go +++ b/internal/ai/vision/model_filters_test.go @@ -40,7 +40,6 @@ func TestFilterModels(t *testing.T) { } for _, tc := range cases { - tc := tc t.Run(tc.name, func(t *testing.T) { got := FilterModels(tc.models, tc.when, tc.allow) if len(got) != len(tc.expected) { diff --git a/internal/ai/vision/model_run_test.go b/internal/ai/vision/model_run_test.go index 69270184b..201b3cb85 100644 --- a/internal/ai/vision/model_run_test.go +++ b/internal/ai/vision/model_run_test.go @@ -21,7 +21,6 @@ func TestParseRunType(t *testing.T) { } for _, tc := range cases { - tc := tc t.Run(tc.name, func(t *testing.T) { if got := ParseRunType(tc.in); got != tc.out { @@ -65,7 +64,6 @@ func TestModel_RunType(t *testing.T) { } for _, tc := range cases { - tc := tc t.Run(tc.name, func(t *testing.T) { if got := tc.model.RunType(); got != tc.want { diff --git a/internal/ai/vision/model_test.go b/internal/ai/vision/model_test.go index 0275739d0..51b9843c5 100644 --- a/internal/ai/vision/model_test.go +++ b/internal/ai/vision/model_test.go @@ -434,7 +434,6 @@ func TestModel_IsDefault(t *testing.T) { } for _, tc := range cases { - tc := tc t.Run(tc.name, func(t *testing.T) { if got := tc.model.IsDefault(); got != tc.want { diff --git a/internal/ai/vision/model_types.go b/internal/ai/vision/model_types.go index ccfb11863..8b802aa00 100644 --- a/internal/ai/vision/model_types.go +++ b/internal/ai/vision/model_types.go @@ -33,7 +33,7 @@ func ParseModelTypes(s string) (types ModelTypes) { s = strings.ToLower(s) types = make(ModelTypes, 0, strings.Count(s, ",")) - for _, t := range strings.Split(s, ",") { + for t := range strings.SplitSeq(s, ",") { t = strings.TrimSpace(t) switch t { case ModelTypeLabels, ModelTypeNsfw, ModelTypeFace, ModelTypeCaption, ModelTypeGenerate: diff --git a/internal/ai/vision/ollama/transport.go b/internal/ai/vision/ollama/transport.go index 024122216..2574e7d52 100644 --- a/internal/ai/vision/ollama/transport.go +++ b/internal/ai/vision/ollama/transport.go @@ -12,7 +12,7 @@ type Response struct { Code int `yaml:"Code,omitempty" json:"code,omitempty"` Error string `yaml:"Error,omitempty" json:"error,omitempty"` Model string `yaml:"Model,omitempty" json:"model,omitempty"` - CreatedAt time.Time `yaml:"CreatedAt,omitempty" json:"created_at,omitempty"` + CreatedAt time.Time `yaml:"CreatedAt,omitempty" json:"created_at"` Response string `yaml:"Response,omitempty" json:"response,omitempty"` Thinking string `yaml:"Thinking,omitempty" json:"thinking,omitempty"` Done bool `yaml:"Done,omitempty" json:"done,omitempty"` @@ -23,7 +23,7 @@ type Response struct { PromptEvalDuration int `yaml:"PromptEvalDuration,omitempty" json:"prompt_eval_duration,omitempty"` EvalCount int `yaml:"EvalCount,omitempty" json:"eval_count,omitempty"` EvalDuration int64 `yaml:"EvalDuration,omitempty" json:"eval_duration,omitempty"` - Result ResultPayload `yaml:"Result,omitempty" json:"result,omitempty"` + Result ResultPayload `yaml:"Result,omitempty" json:"result"` } // Err returns an error if the request has failed. diff --git a/internal/api/abort.go b/internal/api/abort.go index e91c90443..96c0f2a7b 100644 --- a/internal/api/abort.go +++ b/internal/api/abort.go @@ -19,7 +19,7 @@ import ( var brokenVideo []byte // Abort writes a localized error response and stops further handler processing. -func Abort(c *gin.Context, code int, id i18n.Message, params ...interface{}) { +func Abort(c *gin.Context, code int, id i18n.Message, params ...any) { resp := i18n.NewResponse(code, id, params...) if code >= 400 { @@ -32,7 +32,7 @@ func Abort(c *gin.Context, code int, id i18n.Message, params ...interface{}) { } // Error aborts the request while attaching error details to the response payload. -func Error(c *gin.Context, code int, err error, id i18n.Message, params ...interface{}) { +func Error(c *gin.Context, code int, err error, id i18n.Message, params ...any) { resp := i18n.NewResponse(code, id, params...) if err != nil { diff --git a/internal/api/cluster_nodes.go b/internal/api/cluster_nodes.go index d17188bec..2e3f234d9 100644 --- a/internal/api/cluster_nodes.go +++ b/internal/api/cluster_nodes.go @@ -95,11 +95,7 @@ func ClusterListNodes(router *gin.RouterGroup) { offset = len(items) } - end := offset + count - - if end > len(items) { - end = len(items) - } + end := min(offset+count, len(items)) page := items[offset:end] diff --git a/internal/api/oauth_token_ratelimit_test.go b/internal/api/oauth_token_ratelimit_test.go index 16977aa15..20b183cd1 100644 --- a/internal/api/oauth_token_ratelimit_test.go +++ b/internal/api/oauth_token_ratelimit_test.go @@ -32,7 +32,7 @@ func TestOAuthToken_RateLimit_ClientCredentials(t *testing.T) { // Invalid client secret repeatedly (from UnknownIP: no headers set) path := "/api/v1/oauth/token" - for i := 0; i < 3; i++ { + for range 3 { data := url.Values{ "grant_type": {authn.GrantClientCredentials.String()}, "client_id": {"cs5cpu17n6gj2qo5"}, diff --git a/internal/api/session_ratelimit_test.go b/internal/api/session_ratelimit_test.go index 70bad0af4..05ec5e794 100644 --- a/internal/api/session_ratelimit_test.go +++ b/internal/api/session_ratelimit_test.go @@ -24,7 +24,7 @@ func TestCreateSession_RateLimitExceeded(t *testing.T) { limiter.Login = limiter.NewLimit(rate.Every(24*time.Hour), 3) limiter.Auth = limiter.NewLimit(rate.Every(24*time.Hour), 3) - for i := 0; i < 3; i++ { + for range 3 { r := PerformRequestWithBody(app, http.MethodPost, "/api/v1/session", `{"username": "admin", "password": "wrong"}`) assert.Equal(t, http.StatusUnauthorized, r.Code) } diff --git a/internal/api/users_upload_multipart_test.go b/internal/api/users_upload_multipart_test.go index 541d87615..b6b03900c 100644 --- a/internal/api/users_upload_multipart_test.go +++ b/internal/api/users_upload_multipart_test.go @@ -40,7 +40,7 @@ func buildMultipart(files map[string][]byte) (body *bytes.Buffer, contentType st func buildMultipartTwo(name1 string, data1 []byte, name2 string, data2 []byte) (body *bytes.Buffer, contentType string, err error) { body = &bytes.Buffer{} mw := multipart.NewWriter(body) - for _, it := range [][2]interface{}{{name1, data1}, {name2, data2}} { + for _, it := range [][2]any{{name1, data1}, {name2, data2}} { fw, cerr := mw.CreateFormFile("files", it[0].(string)) if cerr != nil { return nil, "", cerr @@ -342,7 +342,7 @@ func TestUploadUserFiles_Multipart_ZipPartialExtraction(t *testing.T) { var zbuf bytes.Buffer zw := zip.NewWriter(&zbuf) - for i := 0; i < 20; i++ { // ~20 * 63 KiB ≈ 1.2 MiB + for i := range 20 { // ~20 * 63 KiB ≈ 1.2 MiB f, _ := zw.Create(fmt.Sprintf("pic%02d.jpg", i+1)) _, _ = f.Write(data) } @@ -390,7 +390,7 @@ func TestUploadUserFiles_Multipart_ZipDeepNestingStress(t *testing.T) { // Build a deeply nested path (20 levels) deep := "" - for i := 0; i < 20; i++ { + for i := range 20 { if i == 0 { deep = "deep" } else { diff --git a/internal/api/websocket_writer.go b/internal/api/websocket_writer.go index 234f215e3..216aabd20 100644 --- a/internal/api/websocket_writer.go +++ b/internal/api/websocket_writer.go @@ -51,7 +51,7 @@ func AppendWebsocketTopics(topics ...string) { } // wsSendMessage sends a message to the WebSocket client. -func wsSendMessage(topic string, data interface{}, ws *websocket.Conn, writeMutex *sync.Mutex) { +func wsSendMessage(topic string, data any, ws *websocket.Conn, writeMutex *sync.Mutex) { if topic == "" || ws == nil || writeMutex == nil { return } diff --git a/internal/auth/acl/roles_test.go b/internal/auth/acl/roles_test.go index b2f90a017..aab6b57a8 100644 --- a/internal/auth/acl/roles_test.go +++ b/internal/auth/acl/roles_test.go @@ -1,6 +1,7 @@ package acl import ( + "slices" "sort" "testing" @@ -172,13 +173,7 @@ func TestResource_Default_String_And_Compare(t *testing.T) { func TestResourceNames_ContainsCore(t *testing.T) { want := []Resource{ResourceDefault, ResourcePhotos, ResourceAlbums, ResourceWebDAV, ResourceApi} for _, w := range want { - found := false - for _, have := range ResourceNames { - if have == w { - found = true - break - } - } + found := slices.Contains(ResourceNames, w) assert.Truef(t, found, "resource %s not found in ResourceNames", w) } } diff --git a/internal/auth/jwt/verifier.go b/internal/auth/jwt/verifier.go index 63608cdd3..3da5c9127 100644 --- a/internal/auth/jwt/verifier.go +++ b/internal/auth/jwt/verifier.go @@ -31,7 +31,7 @@ type VerifierStatus struct { CacheETag string `json:"cacheEtag,omitempty"` KeyIDs []string `json:"keyIds,omitempty"` KeyCount int `json:"keyCount"` - CacheFetchedAt time.Time `json:"cacheFetchedAt,omitempty"` + CacheFetchedAt time.Time `json:"cacheFetchedAt"` CacheAgeSeconds int64 `json:"cacheAgeSeconds"` CacheTTLSeconds int `json:"cacheTtlSeconds"` CacheStale bool `json:"cacheStale"` @@ -137,7 +137,7 @@ func (v *Verifier) VerifyToken(ctx context.Context, tokenString string, expected ) claims := &Claims{} - keyFunc := func(token *gojwt.Token) (interface{}, error) { + keyFunc := func(token *gojwt.Token) (any, error) { kid, _ := token.Header["kid"].(string) if kid == "" { @@ -171,7 +171,7 @@ func (v *Verifier) VerifyToken(ctx context.Context, tokenString string, expected scopeSet := map[string]struct{}{} - for _, s := range strings.Fields(claims.Scope) { + for s := range strings.FieldsSeq(claims.Scope) { scopeSet[s] = struct{}{} } @@ -235,7 +235,7 @@ func VerifyTokenWithKeys(tokenString string, expected ExpectedClaims, keys []Pub parser := gojwt.NewParser(options...) claims := &Claims{} - keyFunc := func(token *gojwt.Token) (interface{}, error) { + keyFunc := func(token *gojwt.Token) (any, error) { kid, _ := token.Header["kid"].(string) if kid == "" { return nil, errors.New("jwt: missing kid header") @@ -261,7 +261,7 @@ func VerifyTokenWithKeys(tokenString string, expected ExpectedClaims, keys []Pub if len(expected.Scope) > 0 { scopeSet := map[string]struct{}{} - for _, s := range strings.Fields(claims.Scope) { + for s := range strings.FieldsSeq(claims.Scope) { scopeSet[s] = struct{}{} } for _, req := range expected.Scope { @@ -543,11 +543,7 @@ func backoffDuration(attempt int) time.Duration { attempt = 1 } - base := jwksFetchBaseDelay << (attempt - 1) - - if base > jwksFetchMaxDelay { - base = jwksFetchMaxDelay - } + base := min(jwksFetchBaseDelay<<(attempt-1), jwksFetchMaxDelay) jitterRange := base / 2 diff --git a/internal/auth/jwt/verifier_test.go b/internal/auth/jwt/verifier_test.go index da5fc0214..4174e018e 100644 --- a/internal/auth/jwt/verifier_test.go +++ b/internal/auth/jwt/verifier_test.go @@ -177,7 +177,7 @@ func TestIssuerClampTTL(t *testing.T) { parsed := &Claims{} parser := gojwt.NewParser(gojwt.WithValidMethods([]string{gojwt.SigningMethodEdDSA.Alg()}), gojwt.WithoutClaimsValidation()) - _, err = parser.ParseWithClaims(token, parsed, func(token *gojwt.Token) (interface{}, error) { + _, err = parser.ParseWithClaims(token, parsed, func(token *gojwt.Token) (any, error) { key, _ := mgr.ActiveKey() return key.PublicKey, nil }) diff --git a/internal/commands/cluster_nodes_list.go b/internal/commands/cluster_nodes_list.go index bd2b0947a..d3cb09c18 100644 --- a/internal/commands/cluster_nodes_list.go +++ b/internal/commands/cluster_nodes_list.go @@ -60,10 +60,7 @@ func clusterNodesListAction(ctx *cli.Context) error { if count <= 0 || count > 1000 { count = 100 } - offset := ctx.Int("offset") - if offset < 0 { - offset = 0 - } + offset := max(ctx.Int("offset"), 0) if offset > len(items) { offset = len(items) } diff --git a/internal/commands/cluster_nodes_mod.go b/internal/commands/cluster_nodes_mod.go index e8bc5315f..d30c6150b 100644 --- a/internal/commands/cluster_nodes_mod.go +++ b/internal/commands/cluster_nodes_mod.go @@ -127,7 +127,7 @@ func clusterNodesModAction(ctx *cli.Context) error { string(acl.ResourceCluster), "update node", "%s", } - args := []interface{}{clean.Log(nodeID)} + args := []any{clean.Log(nodeID)} if changeSummary != "" { segments = append(segments, "%s") diff --git a/internal/commands/cluster_nodes_rotate.go b/internal/commands/cluster_nodes_rotate.go index 78fc8ebff..b4873bae6 100644 --- a/internal/commands/cluster_nodes_rotate.go +++ b/internal/commands/cluster_nodes_rotate.go @@ -194,7 +194,7 @@ func clusterNodesRotateAction(ctx *cli.Context) error { string(acl.ResourceCluster), "rotate node", "%s", } - args := []interface{}{clean.Log(nodeID)} + args := []any{clean.Log(nodeID)} if detail != "" { segments = append(segments, "%s") args = append(args, clean.Log(detail)) diff --git a/internal/commands/cluster_register.go b/internal/commands/cluster_register.go index 727585540..fca25d2d4 100644 --- a/internal/commands/cluster_register.go +++ b/internal/commands/cluster_register.go @@ -332,7 +332,7 @@ func (e *httpError) Error() string { return fmt.Sprintf("http %d: %s", e.Status, func postWithBackoff(url, token string, payload []byte, out any) error { // backoff: 500ms -> max ~8s, 6 attempts with jitter delay := 500 * time.Millisecond - for attempt := 0; attempt < 6; attempt++ { + for range 6 { req, _ := http.NewRequest(http.MethodPost, url, bytes.NewReader(payload)) header.SetAuthorization(req, token) req.Header.Set(header.ContentType, "application/json") diff --git a/internal/commands/commands_test.go b/internal/commands/commands_test.go index 1e0aa3a18..8344a13db 100644 --- a/internal/commands/commands_test.go +++ b/internal/commands/commands_test.go @@ -104,7 +104,7 @@ func NewTestContext(args []string) *cli.Context { app.HideHelpCommand = true app.Action = func(*cli.Context) error { return nil } app.EnableBashCompletion = false - app.Metadata = map[string]interface{}{ + app.Metadata = map[string]any{ "Name": "PhotoPrism", "About": "PhotoPrism®", "Edition": "ce", diff --git a/internal/commands/find.go b/internal/commands/find.go index 5395f61d1..b9a2d0cd3 100644 --- a/internal/commands/find.go +++ b/internal/commands/find.go @@ -74,10 +74,7 @@ func findAction(ctx *cli.Context) error { rows := make([][]string, 0, len(results)) for _, found := range results { - size := found.FileSize - if size < 0 { - size = 0 - } + size := max(found.FileSize, 0) v := []string{found.FileName, found.FileMime, humanize.Bytes(uint64(size)), found.FileHash} //nolint:gosec // size non-negative after check rows = append(rows, v) } diff --git a/internal/commands/show_commands.go b/internal/commands/show_commands.go index a755498be..e4d0e9542 100644 --- a/internal/commands/show_commands.go +++ b/internal/commands/show_commands.go @@ -38,10 +38,7 @@ func showCommandsAction(ctx *cli.Context) error { includeHidden := ctx.Bool("all") wantJSON := ctx.Bool("json") nested := ctx.Bool("nested") - baseHeading := ctx.Int("base-heading") - if baseHeading < 1 { - baseHeading = 1 - } + baseHeading := max(ctx.Int("base-heading"), 1) // Collect the app metadata to be included in the output. app := catalog.App{} diff --git a/internal/commands/show_commands_test.go b/internal/commands/show_commands_test.go index 5ac6d993d..a5c41c58e 100644 --- a/internal/commands/show_commands_test.go +++ b/internal/commands/show_commands_test.go @@ -40,7 +40,7 @@ func TestShowCommands_JSON_Flat(t *testing.T) { Name, FullName string Depth int } `json:"commands"` - GlobalFlags []map[string]interface{} `json:"global_flags"` + GlobalFlags []map[string]any `json:"global_flags"` } if err := json.Unmarshal(b, &v); err != nil { t.Fatalf("invalid json: %v", err) diff --git a/internal/commands/show_config.go b/internal/commands/show_config.go index 8d5840acb..73025fdd4 100644 --- a/internal/commands/show_config.go +++ b/internal/commands/show_config.go @@ -58,7 +58,7 @@ func showConfigAction(ctx *cli.Context) error { rows, cols := rep.Report(conf) sections = append(sections, section{Title: rep.Title, Items: report.RowsToObjects(rows, cols)}) } - b, _ := json.Marshal(map[string]interface{}{"sections": sections}) + b, _ := json.Marshal(map[string]any{"sections": sections}) fmt.Println(string(b)) return nil } diff --git a/internal/commands/show_json_test.go b/internal/commands/show_json_test.go index 5da26d7f7..71c566fd2 100644 --- a/internal/commands/show_json_test.go +++ b/internal/commands/show_json_test.go @@ -184,13 +184,6 @@ func TestShowConfigOptions_MarkdownSections(t *testing.T) { } } -func min(a, b int) int { - if a < b { - return a - } - return b -} - func TestShowFileFormats_JSON(t *testing.T) { out, err := RunWithTestContext(ShowFileFormatsCommand, []string{"file-formats", "--json"}) diff --git a/internal/commands/video_helpers.go b/internal/commands/video_helpers.go index 1fa7b24bb..f582aa363 100644 --- a/internal/commands/video_helpers.go +++ b/internal/commands/video_helpers.go @@ -234,10 +234,10 @@ func videoListRow(found search.Photo) []string { } // videoListJSONRow renders a search result row for JSON output with canonical column keys. -func videoListJSONRow(found search.Photo) map[string]interface{} { +func videoListJSONRow(found search.Photo) map[string]any { videoFile, _ := videoPrimaryFile(found) - data := map[string]interface{}{ + data := map[string]any{ "video": videoFile.FileName, "size": videoNonNegativeSize(videoFile.FileSize), "resolution": fmt.Sprintf("%dx%d", videoFile.FileWidth, videoFile.FileHeight), @@ -252,16 +252,16 @@ func videoListJSONRow(found search.Photo) map[string]interface{} { } // videoListJSON marshals a list of JSON rows using the canonical keys for each column. -func videoListJSON(rows []map[string]interface{}, cols []string) (string, error) { +func videoListJSON(rows []map[string]any, cols []string) (string, error) { canon := make([]string, len(cols)) for i, col := range cols { canon[i] = report.CanonKey(col) } - payload := make([]map[string]interface{}, 0, len(rows)) + payload := make([]map[string]any, 0, len(rows)) for _, row := range rows { - item := make(map[string]interface{}, len(canon)) + item := make(map[string]any, len(canon)) for _, key := range canon { item[key] = row[key] } diff --git a/internal/commands/video_info.go b/internal/commands/video_info.go index 73bbd22be..75bcf2c25 100644 --- a/internal/commands/video_info.go +++ b/internal/commands/video_info.go @@ -68,10 +68,10 @@ func videoInfoAction(ctx *cli.Context) error { // videoInfoEntry describes all metadata sections for a single video. type videoInfoEntry struct { - Index map[string]interface{} `json:"index"` - Exif interface{} `json:"exif,omitempty"` - FFprobe interface{} `json:"ffprobe,omitempty"` - Raw map[string]string `json:"raw,omitempty"` + Index map[string]any `json:"index"` + Exif any `json:"exif,omitempty"` + FFprobe any `json:"ffprobe,omitempty"` + Raw map[string]string `json:"raw,omitempty"` } // videoInfoEntryFor collects indexed, ExifTool, and ffprobe metadata for a search result. @@ -123,8 +123,8 @@ func videoInfoEntryFor(conf *config.Config, found search.Photo, verbose bool) (v } // videoIndexSummary builds a concise map of indexed fields for diagnostics. -func videoIndexSummary(found search.Photo, file entity.File) map[string]interface{} { - return map[string]interface{}{ +func videoIndexSummary(found search.Photo, file entity.File) map[string]any { + return map[string]any{ "file_name": file.FileName, "file_root": file.FileRoot, "file_uid": file.FileUID, @@ -152,7 +152,7 @@ func videoIndexSummary(found search.Photo, file entity.File) map[string]interfac } // videoRunFFprobe executes ffprobe and returns parsed JSON plus raw output. -func videoRunFFprobe(ffprobeBin, filePath string) (interface{}, string, error) { +func videoRunFFprobe(ffprobeBin, filePath string) (any, string, error) { cmd := exec.Command(ffprobeBin, "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", filePath) //nolint:gosec // args are validated paths var stdout bytes.Buffer @@ -169,7 +169,7 @@ func videoRunFFprobe(ffprobeBin, filePath string) (interface{}, string, error) { return nil, raw, nil } - var data interface{} + var data any if err := json.Unmarshal([]byte(raw), &data); err != nil { return nil, raw, nil } @@ -185,7 +185,7 @@ func (v *videoInfoEntry) ensureRaw() { } // videoPrettyJSON returns indented JSON for human-readable output. -func videoPrettyJSON(value interface{}) string { +func videoPrettyJSON(value any) string { data, err := json.MarshalIndent(value, "", " ") if err != nil { return "" diff --git a/internal/commands/video_ls.go b/internal/commands/video_ls.go index b36f5d2e8..8b596609f 100644 --- a/internal/commands/video_ls.go +++ b/internal/commands/video_ls.go @@ -45,7 +45,7 @@ func videoListAction(ctx *cli.Context) error { cols := videoListColumns() if format == report.JSON { - rows := make([]map[string]interface{}, 0, len(results)) + rows := make([]map[string]any, 0, len(results)) for _, found := range results { rows = append(rows, videoListJSONRow(found)) } diff --git a/internal/commands/video_search.go b/internal/commands/video_search.go index b9ba0663e..4baf7e59b 100644 --- a/internal/commands/video_search.go +++ b/internal/commands/video_search.go @@ -27,20 +27,12 @@ func videoSearchResults(query string, count int, offset int) ([]search.Photo, er Order: sortby.Name, } - target := count + offset - - if target < 0 { - target = 0 - } + target := max(count+offset, 0) collected := make([]search.Photo, 0, target) index := make(map[string]int, target) searchOffset := 0 - batchSize := count - - if batchSize < 200 { - batchSize = 200 - } + batchSize := max(count, 200) needComplete := false diff --git a/internal/config/cli_context.go b/internal/config/cli_context.go index 353765796..f0c8896ea 100644 --- a/internal/config/cli_context.go +++ b/internal/config/cli_context.go @@ -11,7 +11,7 @@ import ( ) // ApplyCliContext applies the values of the cli context based on the "flag" annotations. -func ApplyCliContext(c interface{}, ctx *cli.Context) error { +func ApplyCliContext(c any, ctx *cli.Context) error { v := reflect.ValueOf(c).Elem() // Iterate through all config fields. diff --git a/internal/config/cli_context_test.go b/internal/config/cli_context_test.go index c5c068a5f..968034cca 100644 --- a/internal/config/cli_context_test.go +++ b/internal/config/cli_context_test.go @@ -25,7 +25,6 @@ func TestApplyCliContext_Duration(t *testing.T) { } for _, tc := range tests { - tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() diff --git a/internal/config/cli_flag.go b/internal/config/cli_flag.go index 3a92b2cb1..14b2d8950 100644 --- a/internal/config/cli_flag.go +++ b/internal/config/cli_flag.go @@ -25,7 +25,7 @@ func (f CliFlag) Skip(tags []string) bool { func (f CliFlag) Fields() reflect.Value { fields := reflect.ValueOf(f.Flag) - for fields.Kind() == reflect.Ptr { + for fields.Kind() == reflect.Pointer { fields = reflect.Indirect(fields) } diff --git a/internal/config/cli_flags.go b/internal/config/cli_flags.go index 3dcc5af50..1a7c3a8ee 100644 --- a/internal/config/cli_flags.go +++ b/internal/config/cli_flags.go @@ -157,7 +157,7 @@ func (f CliFlags) SetHidden(hidden bool, names ...string) (result CliFlags) { if list.Contains(names, flag.Name()) { rv := reflect.ValueOf(flag.Flag) - if rv.Kind() == reflect.Ptr { + if rv.Kind() == reflect.Pointer { rv = rv.Elem() } diff --git a/internal/config/config.go b/internal/config/config.go index 68946a6d0..d519b691c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -86,7 +86,7 @@ type Config struct { } // Values is a shorthand alias for map[string]interface{}. -type Values = map[string]interface{} +type Values = map[string]any func init() { TotalMem = memory.TotalMemory() @@ -747,12 +747,9 @@ func (c *Config) IndexWorkers() int { } // NumCPU returns the number of logical CPU cores. - cores := runtime.NumCPU() - - // Limit to physical cores to avoid high load on HT capable CPUs. - if cores > cpuid.CPU.PhysicalCores { - cores = cpuid.CPU.PhysicalCores - } + cores := min( + // Limit to physical cores to avoid high load on HT capable CPUs. + runtime.NumCPU(), cpuid.CPU.PhysicalCores) // Limit number of workers when using SQLite3 to avoid database locking issues. if c.DatabaseDriver() == SQLite3 && (cores >= 8 && c.options.IndexWorkers <= 0 || c.options.IndexWorkers > 4) { diff --git a/internal/config/config_faces.go b/internal/config/config_faces.go index 75ce7d294..993ee4544 100644 --- a/internal/config/config_faces.go +++ b/internal/config/config_faces.go @@ -126,10 +126,7 @@ func (c *Config) FaceEngineThreads() int { if c == nil { return 1 } else if c.options.FaceEngineThreads <= 0 { - threads := runtime.NumCPU() / 2 - if threads < 1 { - threads = 1 - } + threads := max(runtime.NumCPU()/2, 1) c.options.FaceEngineThreads = threads diff --git a/internal/config/config_faces_test.go b/internal/config/config_faces_test.go index 809da49e8..2e80b5e92 100644 --- a/internal/config/config_faces_test.go +++ b/internal/config/config_faces_test.go @@ -162,10 +162,7 @@ func TestConfig_FaceEngineRunType(t *testing.T) { func TestConfig_FaceEngineThreads(t *testing.T) { c := NewConfig(CliTestContext()) - expected := runtime.NumCPU() / 2 - if expected < 1 { - expected = 1 - } + expected := max(runtime.NumCPU()/2, 1) assert.Equal(t, expected, c.FaceEngineThreads()) c.options.FaceEngineThreads = 8 diff --git a/internal/config/config_site.go b/internal/config/config_site.go index f178fd10e..cafbefb28 100644 --- a/internal/config/config_site.go +++ b/internal/config/config_site.go @@ -199,7 +199,7 @@ func (c *Config) LegalUrl() string { func (c *Config) RobotsTxt() ([]byte, error) { if c.Demo() && c.Public() { // Allow public demo instances to be indexed. - return []byte(fmt.Sprintf("User-agent: *\nDisallow: /\nAllow: %s/\nAllow: %s/\nAllow: .js\nAllow: .css", LibraryUri, StaticUri)), nil + return fmt.Appendf(nil, "User-agent: *\nDisallow: /\nAllow: %s/\nAllow: %s/\nAllow: .js\nAllow: .css", LibraryUri, StaticUri), nil } else if c.Public() { // Do not allow other instances to be indexed when public mode is enabled. return robotsTxt, nil diff --git a/internal/config/config_usage.go b/internal/config/config_usage.go index 37d9fc90e..7feb8588f 100644 --- a/internal/config/config_usage.go +++ b/internal/config/config_usage.go @@ -111,22 +111,14 @@ func (c *Config) Usage() Usage { info.FilesUsedPct = 1 } - info.FilesFreePct = 100 - info.FilesUsedPct - - if info.FilesFreePct < 0 { - info.FilesFreePct = 0 - } + info.FilesFreePct = max(100-info.FilesUsedPct, 0) info.UsersActive = query.CountUsers(true, true, nil, []string{"guest"}) info.GuestsActive = query.CountUsers(true, true, []string{"guest"}, nil) if info.UsersQuota = c.UsersQuota(); info.UsersQuota > 0 { info.UsersUsedPct = int(math.Floor(info.UsersUsedRatio() * 100)) - info.UsersFreePct = 100 - info.UsersUsedPct - - if info.UsersFreePct < 0 { - info.UsersFreePct = 0 - } + info.UsersFreePct = max(100-info.UsersUsedPct, 0) } else { info.UsersUsedPct = 0 info.UsersFreePct = 0 diff --git a/internal/config/pwa/manifest.go b/internal/config/pwa/manifest.go index 165f5a2e4..3a1d8b573 100644 --- a/internal/config/pwa/manifest.go +++ b/internal/config/pwa/manifest.go @@ -25,7 +25,7 @@ type Manifest struct { Scope string `json:"scope"` StartUrl string `json:"start_url,omitempty"` Shortcuts Urls `json:"shortcuts"` - Serviceworker Serviceworker `json:"serviceworker,omitempty"` + Serviceworker Serviceworker `json:"serviceworker"` Permissions list.List `json:"permissions"` OptionalPermissions list.List `json:"optional_permissions"` HostPermissions []string `json:"host_permissions"` diff --git a/internal/entity/album.go b/internal/entity/album.go index 487be86e3..edd35b908 100644 --- a/internal/entity/album.go +++ b/internal/entity/album.go @@ -99,7 +99,7 @@ func (Album) TableName() string { } // UpdateAlbum updates album attributes directly in the database by UID. -func UpdateAlbum(albumUID string, values interface{}) (err error) { +func UpdateAlbum(albumUID string, values any) (err error) { if rnd.InvalidUID(albumUID, AlbumUID) { return fmt.Errorf("album: invalid uid %s", clean.Log(albumUID)) } else if err = Db().Model(Album{}).Where("album_uid = ?", albumUID).UpdateColumns(values).Error; err != nil { @@ -727,7 +727,7 @@ func (m *Album) SaveForm(f *form.Album) error { } // Update sets a new value for a database column. -func (m *Album) Update(attr string, value interface{}) error { +func (m *Album) Update(attr string, value any) error { if m == nil { return errors.New("album must not be nil - you may have found a bug") } else if !m.HasID() { @@ -738,7 +738,7 @@ func (m *Album) Update(attr string, value interface{}) error { } // Updates multiple columns in the database. -func (m *Album) Updates(values interface{}) error { +func (m *Album) Updates(values any) error { if m == nil { return errors.New("album must not be nil - you may have found a bug") } else if !m.HasID() { diff --git a/internal/entity/auth_client.go b/internal/entity/auth_client.go index dcea228ef..ab13e8f2e 100644 --- a/internal/entity/auth_client.go +++ b/internal/entity/auth_client.go @@ -361,7 +361,7 @@ func (m *Client) Disabled() bool { } // Updates multiple properties in the database. -func (m *Client) Updates(values interface{}) error { +func (m *Client) Updates(values any) error { return UnscopedDb().Model(m).Updates(values).Error } diff --git a/internal/entity/auth_session.go b/internal/entity/auth_session.go index 7947129d3..5e4b155e2 100644 --- a/internal/entity/auth_session.go +++ b/internal/entity/auth_session.go @@ -272,7 +272,7 @@ func (m *Session) Delete() error { } // Updates multiple properties in the database. -func (m *Session) Updates(values interface{}) error { +func (m *Session) Updates(values any) error { return UnscopedDb().Model(m).Updates(values).Error } diff --git a/internal/entity/auth_session_data.go b/internal/entity/auth_session_data.go index 7754738ca..f1ce521ed 100644 --- a/internal/entity/auth_session_data.go +++ b/internal/entity/auth_session_data.go @@ -1,6 +1,7 @@ package entity import ( + "slices" "strings" ) @@ -86,13 +87,7 @@ func (data SessionData) HasShare(uid string) bool { return false } - for _, share := range data.Shares { - if share == uid { - return true - } - } - - return false + return slices.Contains(data.Shares, uid) } // SharedUIDs returns shared entity UIDs. diff --git a/internal/entity/auth_session_delete_test.go b/internal/entity/auth_session_delete_test.go index 1221646ae..b260ddfba 100644 --- a/internal/entity/auth_session_delete_test.go +++ b/internal/entity/auth_session_delete_test.go @@ -57,7 +57,7 @@ func TestDeleteClientSessions(t *testing.T) { assert.Equal(t, 0, DeleteClientSessions(&Client{}, authn.MethodDefault, 0)) // Create 10 test client sessions. - for i := 0; i < 10; i++ { + for range 10 { sess := NewSession(3600, 0) sess.SetClient(client) diff --git a/internal/entity/auth_user.go b/internal/entity/auth_user.go index 427b00e1a..033068822 100644 --- a/internal/entity/auth_user.go +++ b/internal/entity/auth_user.go @@ -378,7 +378,7 @@ func (m *User) SaveRelated() *User { } // Updates multiple properties in the database. -func (m *User) Updates(values interface{}) error { +func (m *User) Updates(values any) error { return UnscopedDb().Model(m).Updates(values).Error } diff --git a/internal/entity/auth_user_details.go b/internal/entity/auth_user_details.go index 0033bcd44..e5f245f2b 100644 --- a/internal/entity/auth_user_details.go +++ b/internal/entity/auth_user_details.go @@ -98,7 +98,7 @@ func (m *UserDetails) Save() error { } // Updates multiple properties in the database. -func (m *UserDetails) Updates(values interface{}) error { +func (m *UserDetails) Updates(values any) error { return UnscopedDb().Model(m).Updates(values).Error } diff --git a/internal/entity/auth_user_settings.go b/internal/entity/auth_user_settings.go index f6d0c8222..bd65f6062 100644 --- a/internal/entity/auth_user_settings.go +++ b/internal/entity/auth_user_settings.go @@ -78,7 +78,7 @@ func (m *UserSettings) Save() error { } // Updates multiple properties in the database. -func (m *UserSettings) Updates(values interface{}) error { +func (m *UserSettings) Updates(values any) error { return UnscopedDb().Model(m).Updates(values).Error } diff --git a/internal/entity/auth_user_share.go b/internal/entity/auth_user_share.go index e6769e668..8c81e8317 100644 --- a/internal/entity/auth_user_share.go +++ b/internal/entity/auth_user_share.go @@ -144,7 +144,7 @@ func (m *UserShare) Save() error { } // Updates changes multiple record values. -func (m *UserShare) Updates(values interface{}) error { +func (m *UserShare) Updates(values any) error { return UnscopedDb().Model(m).Updates(values).Error } diff --git a/internal/entity/details.go b/internal/entity/details.go index cefc5e907..d39520508 100644 --- a/internal/entity/details.go +++ b/internal/entity/details.go @@ -64,7 +64,7 @@ func (m *Details) Save() error { } // Update a column in the database. -func (m *Details) Update(attr string, value interface{}) error { +func (m *Details) Update(attr string, value any) error { if m == nil { return errors.New("photo details must not be nil - you may have found a bug") } else if m.PhotoID < 1 { @@ -75,7 +75,7 @@ func (m *Details) Update(attr string, value interface{}) error { } // Updates multiple columns in the database. -func (m *Details) Updates(values interface{}) error { +func (m *Details) Updates(values any) error { if values == nil { return nil } else if m == nil { diff --git a/internal/entity/entity_count.go b/internal/entity/entity_count.go index 904c55fa0..4d23e4010 100644 --- a/internal/entity/entity_count.go +++ b/internal/entity/entity_count.go @@ -5,7 +5,7 @@ import ( ) // Count returns the number of records for a given a model and key values. -func Count(m interface{}, keys []string, values []interface{}) int { +func Count(m any, keys []string, values []any) int { if m == nil || len(keys) != len(values) { log.Debugf("entity: invalid parameters (count records)") return -1 diff --git a/internal/entity/entity_save.go b/internal/entity/entity_save.go index ccbb8efcf..06dda2a27 100644 --- a/internal/entity/entity_save.go +++ b/internal/entity/entity_save.go @@ -6,7 +6,7 @@ import ( ) // Save tries to update an existing record and falls back to insert semantics, retrying on lock errors. -func Save(m interface{}, keyNames ...string) (err error) { +func Save(m any, keyNames ...string) (err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("save: %s (panic)", r) diff --git a/internal/entity/entity_tables.go b/internal/entity/entity_tables.go index cfcd5257c..80bb1a795 100644 --- a/internal/entity/entity_tables.go +++ b/internal/entity/entity_tables.go @@ -11,7 +11,7 @@ import ( "github.com/photoprism/photoprism/pkg/clean" ) -type Tables map[string]interface{} +type Tables map[string]any // Entities contains database entities and their table names. var Entities = Tables{ @@ -105,7 +105,7 @@ func (list Tables) Truncate(db *gorm.DB) { // Migrate migrates all database tables of registered entities. func (list Tables) Migrate(db *gorm.DB, opt migrate.Options) { var name string - var entity interface{} + var entity any defer func() { if r := recover(); r != nil { diff --git a/internal/entity/entity_update.go b/internal/entity/entity_update.go index 6674765fa..c105c2219 100644 --- a/internal/entity/entity_update.go +++ b/internal/entity/entity_update.go @@ -5,7 +5,7 @@ import ( ) // Update updates the values of an existing database record. -func Update(m interface{}, keyNames ...string) (err error) { +func Update(m any, keyNames ...string) (err error) { // Use an unscoped *gorm.DB connection, so that // soft-deleted database records can also be updated. db := UnscopedDb() diff --git a/internal/entity/entity_values.go b/internal/entity/entity_values.go index d231fc2f1..e97576b70 100644 --- a/internal/entity/entity_values.go +++ b/internal/entity/entity_values.go @@ -3,25 +3,20 @@ package entity import ( "fmt" "reflect" + "slices" ) // Values is a shorthand alias for map[string]interface{}. -type Values = map[string]interface{} +type Values = map[string]any // Map is retained for backward compatibility. // TODO: Remove when no longer needed. type Map = Values // ModelValues extracts exported struct fields into a Values map, optionally omitting selected names. -func ModelValues(m interface{}, omit ...string) (result Values, omitted []interface{}, err error) { +func ModelValues(m any, omit ...string) (result Values, omitted []any, err error) { mustOmit := func(name string) bool { - for _, s := range omit { - if name == s { - return true - } - } - - return false + return slices.Contains(omit, name) } r := reflect.ValueOf(m) @@ -39,11 +34,11 @@ func ModelValues(m interface{}, omit ...string) (result Values, omitted []interf t := values.Type() num := t.NumField() - omitted = make([]interface{}, 0, len(omit)) + omitted = make([]any, 0, len(omit)) result = make(Values, num) // Add exported fields to result. - for i := 0; i < num; i++ { + for i := range num { field := t.Field(i) // Skip non-exported fields. diff --git a/internal/entity/face.go b/internal/entity/face.go index c57c849c0..ac2326495 100644 --- a/internal/entity/face.go +++ b/internal/entity/face.go @@ -396,7 +396,7 @@ func (m *Face) Delete() error { } // Update a face property in the database. -func (m *Face) Update(attr string, value interface{}) error { +func (m *Face) Update(attr string, value any) error { if m.ID == "" { return fmt.Errorf("empty id") } @@ -407,7 +407,7 @@ func (m *Face) Update(attr string, value interface{}) error { } // Updates face properties in the database. -func (m *Face) Updates(values interface{}) error { +func (m *Face) Updates(values any) error { if m.ID == "" { return fmt.Errorf("empty id") } diff --git a/internal/entity/file.go b/internal/entity/file.go index 066571081..1d3c05c02 100644 --- a/internal/entity/file.go +++ b/internal/entity/file.go @@ -516,12 +516,12 @@ func (m *File) UpdateVideoInfos() error { } // Update updates a column in the database. -func (m *File) Update(attr string, value interface{}) error { +func (m *File) Update(attr string, value any) error { return UnscopedDb().Model(m).UpdateColumn(attr, value).Error } // Updates multiple columns in the database. -func (m *File) Updates(values interface{}) error { +func (m *File) Updates(values any) error { return UnscopedDb().Model(m).UpdateColumns(values).Error } diff --git a/internal/entity/file_json.go b/internal/entity/file_json.go index d3d243464..ebfd1ae53 100644 --- a/internal/entity/file_json.go +++ b/internal/entity/file_json.go @@ -49,12 +49,12 @@ func (m *File) MarshalJSON() ([]byte, error) { Software string `json:",omitempty"` Error string `json:",omitempty"` ModTime int64 `json:",omitempty"` - CreatedAt time.Time `json:",omitempty"` - CreatedIn int64 `json:",omitempty"` - UpdatedAt time.Time `json:",omitempty"` - UpdatedIn int64 `json:",omitempty"` - DeletedAt *time.Time `json:",omitempty"` - Markers *Markers `json:",omitempty"` + CreatedAt time.Time + CreatedIn int64 `json:",omitempty"` + UpdatedAt time.Time + UpdatedIn int64 `json:",omitempty"` + DeletedAt *time.Time `json:",omitempty"` + Markers *Markers `json:",omitempty"` }{ UID: m.FileUID, PhotoUID: m.PhotoUID, diff --git a/internal/entity/file_share.go b/internal/entity/file_share.go index 15e0803c2..bb46441e4 100644 --- a/internal/entity/file_share.go +++ b/internal/entity/file_share.go @@ -45,12 +45,12 @@ func NewFileShare(fileID, accountID uint, remoteName string) *FileShare { } // Updates mutates multiple columns on the existing row. -func (m *FileShare) Updates(values interface{}) error { +func (m *FileShare) Updates(values any) error { return UnscopedDb().Model(m).UpdateColumns(values).Error } // Update mutates a single column on the existing row. -func (m *FileShare) Update(attr string, value interface{}) error { +func (m *FileShare) Update(attr string, value any) error { return UnscopedDb().Model(m).UpdateColumn(attr, value).Error } diff --git a/internal/entity/file_sync.go b/internal/entity/file_sync.go index e7fab2ced..883c15e68 100644 --- a/internal/entity/file_sync.go +++ b/internal/entity/file_sync.go @@ -18,7 +18,7 @@ type FileSync struct { RemoteName string `gorm:"primary_key;auto_increment:false;type:VARBINARY(255)" json:"RemoteName" yaml:"RemoteName,omitempty"` ServiceID uint `gorm:"primary_key;auto_increment:false" json:"ServiceID" yaml:"ServiceID,omitempty"` FileID uint `gorm:"index;" json:"FileID" yaml:"FileID,omitempty"` - RemoteDate time.Time `json:"RemoteDate,omitempty" yaml:"RemoteDate,omitempty"` + RemoteDate time.Time `json:"RemoteDate" yaml:"RemoteDate,omitempty"` RemoteSize int64 `json:"RemoteSize,omitempty" yaml:"RemoteSize,omitempty"` Status string `gorm:"type:VARBINARY(16);" json:"Status" yaml:"Status,omitempty"` Error string `gorm:"type:VARBINARY(512);" json:"Error,omitempty" yaml:"Error,omitempty"` @@ -46,12 +46,12 @@ func NewFileSync(accountID uint, remoteName string) *FileSync { } // Updates mutates multiple columns on the existing row. -func (m *FileSync) Updates(values interface{}) error { +func (m *FileSync) Updates(values any) error { return UnscopedDb().Model(m).UpdateColumns(values).Error } // Update mutates a single column on the existing row. -func (m *FileSync) Update(attr string, value interface{}) error { +func (m *FileSync) Update(attr string, value any) error { return UnscopedDb().Model(m).UpdateColumn(attr, value).Error } diff --git a/internal/entity/folder.go b/internal/entity/folder.go index 6d4bbd90a..49b761b41 100644 --- a/internal/entity/folder.go +++ b/internal/entity/folder.go @@ -42,7 +42,7 @@ type Folder struct { FileCount int `gorm:"-" json:"FileCount" yaml:"-"` CreatedAt time.Time `json:"-" yaml:"-"` UpdatedAt time.Time `json:"-" yaml:"-"` - ModifiedAt time.Time `json:"ModifiedAt,omitempty" yaml:"-"` + ModifiedAt time.Time `json:"ModifiedAt" yaml:"-"` PublishedAt *time.Time `sql:"index" json:"PublishedAt,omitempty" yaml:"PublishedAt,omitempty"` DeletedAt *time.Time `sql:"index" json:"-" yaml:"DeletedAt,omitempty"` } @@ -230,7 +230,7 @@ func FirstOrCreateFolder(m *Folder) *Folder { } // Updates selected properties in the database. -func (m *Folder) Updates(values interface{}) error { +func (m *Folder) Updates(values any) error { return Db().Model(m).Updates(values).Error } diff --git a/internal/entity/keyword.go b/internal/entity/keyword.go index 7610074f7..d28f62273 100644 --- a/internal/entity/keyword.go +++ b/internal/entity/keyword.go @@ -37,7 +37,7 @@ func NewKeyword(keyword string) *Keyword { // Update modifies a single column on an already persisted keyword and relies on // the standard GORM callback to evict the cached instance afterwards. -func (m *Keyword) Update(attr string, value interface{}) error { +func (m *Keyword) Update(attr string, value any) error { if m == nil { return errors.New("keyword must not be nil - you may have found a bug") } else if !m.HasID() { @@ -50,7 +50,7 @@ func (m *Keyword) Update(attr string, value interface{}) error { // Updates applies a set of column changes to an existing keyword while keeping // the cache consistent via the AfterUpdate hook. -func (m *Keyword) Updates(values interface{}) error { +func (m *Keyword) Updates(values any) error { if values == nil { return nil } else if m == nil { diff --git a/internal/entity/label.go b/internal/entity/label.go index c6f62ba7f..657f51063 100644 --- a/internal/entity/label.go +++ b/internal/entity/label.go @@ -201,7 +201,7 @@ func (m *Label) Skip() bool { } // Update a label property in the database. -func (m *Label) Update(attr string, value interface{}) error { +func (m *Label) Update(attr string, value any) error { if m == nil { return errors.New("label must not be nil - you may have found a bug") } else if !m.HasID() { @@ -212,7 +212,7 @@ func (m *Label) Update(attr string, value interface{}) error { } // Updates multiple columns in the database. -func (m *Label) Updates(values interface{}) error { +func (m *Label) Updates(values any) error { if values == nil { return nil } else if m == nil { diff --git a/internal/entity/marker.go b/internal/entity/marker.go index 21d7b9a27..8153e6705 100644 --- a/internal/entity/marker.go +++ b/internal/entity/marker.go @@ -142,13 +142,13 @@ func (m *Marker) UpdateFile(file *File) (updated bool) { } // Updates multiple columns in the database. -func (m *Marker) Updates(values interface{}) error { +func (m *Marker) Updates(values any) error { UpdateFaces.Store(true) return UnscopedDb().Model(m).Updates(values).Error } // Update updates a column in the database. -func (m *Marker) Update(attr string, value interface{}) error { +func (m *Marker) Update(attr string, value any) error { UpdateFaces.Store(true) return UnscopedDb().Model(m).Update(attr, value).Error } diff --git a/internal/entity/migrate/generate.go b/internal/entity/migrate/generate.go index 24bc4dc4e..55a1197ae 100644 --- a/internal/entity/migrate/generate.go +++ b/internal/entity/migrate/generate.go @@ -1,5 +1,4 @@ //go:build ignore -// +build ignore // This generates countries.go by running "go generate" package main diff --git a/internal/entity/migrate/migrate.go b/internal/entity/migrate/migrate.go index e27106faf..6dfe6f7ee 100644 --- a/internal/entity/migrate/migrate.go +++ b/internal/entity/migrate/migrate.go @@ -34,4 +34,4 @@ import ( var log = event.Log // Map is a shorthand alias for map[string]interface{}. -type Map = map[string]interface{} +type Map = map[string]any diff --git a/internal/entity/passcode.go b/internal/entity/passcode.go index 3aae0b95b..07672df93 100644 --- a/internal/entity/passcode.go +++ b/internal/entity/passcode.go @@ -105,7 +105,7 @@ func (m *Passcode) Delete() (err error) { } // Updates multiple properties in the database. -func (m *Passcode) Updates(values interface{}) error { +func (m *Passcode) Updates(values any) error { return UnscopedDb().Model(m).Updates(values).Error } diff --git a/internal/entity/photo.go b/internal/entity/photo.go index 5317f9a8c..014594db2 100644 --- a/internal/entity/photo.go +++ b/internal/entity/photo.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "path" + "slices" "strconv" "strings" "sync" @@ -99,8 +100,8 @@ type Photo struct { Files []File `yaml:"-"` Labels []PhotoLabel `yaml:"-"` CreatedBy string `gorm:"type:VARBINARY(42);index" json:"CreatedBy,omitempty" yaml:"CreatedBy,omitempty"` - CreatedAt time.Time `json:"CreatedAt,omitempty" yaml:"CreatedAt,omitempty"` - UpdatedAt time.Time `json:"UpdatedAt,omitempty" yaml:"UpdatedAt,omitempty"` + CreatedAt time.Time `json:"CreatedAt" yaml:"CreatedAt,omitempty"` + UpdatedAt time.Time `json:"UpdatedAt" yaml:"UpdatedAt,omitempty"` EditedAt *time.Time `json:"EditedAt,omitempty" yaml:"EditedAt,omitempty"` PublishedAt *time.Time `sql:"index" json:"PublishedAt,omitempty" yaml:"PublishedAt,omitempty"` IndexedAt *time.Time `json:"IndexedAt,omitempty" yaml:"-"` @@ -332,7 +333,7 @@ func (m *Photo) Save() error { } // Update a column in the database. -func (m *Photo) Update(attr string, value interface{}) error { +func (m *Photo) Update(attr string, value any) error { if m == nil { return errors.New("photo must not be nil - you may have found a bug") } else if !m.HasID() { @@ -343,7 +344,7 @@ func (m *Photo) Update(attr string, value interface{}) error { } // Updates multiple columns in the database. -func (m *Photo) Updates(values interface{}) error { +func (m *Photo) Updates(values any) error { if values == nil { return nil } else if m == nil { @@ -411,13 +412,7 @@ func (m *Photo) ResetDuration() { func (m *Photo) HasMediaType(types ...media.Type) bool { mediaType := m.MediaType() - for _, t := range types { - if mediaType == t { - return true - } - } - - return false + return slices.Contains(types, mediaType) } // SetMediaType sets a new media type if its priority is higher than that of the current type. @@ -944,11 +939,7 @@ func (m *Photo) AddLabels(labels classify.Labels) { template := NewPhotoLabel(m.ID, labelEntity.ID, classifyLabel.Uncertainty, labelSrc) template.Topicality = classifyLabel.Topicality - score := 0 - - if classifyLabel.NSFWConfidence > 0 { - score = classifyLabel.NSFWConfidence - } + score := max(classifyLabel.NSFWConfidence, 0) if classifyLabel.NSFW && score == 0 { score = 100 @@ -979,10 +970,7 @@ func (m *Photo) AddLabels(labels classify.Labels) { } if classifyLabel.NSFWConfidence > 0 || classifyLabel.NSFW { - nsfwScore := 0 - if classifyLabel.NSFWConfidence > 0 { - nsfwScore = classifyLabel.NSFWConfidence - } + nsfwScore := max(classifyLabel.NSFWConfidence, 0) if classifyLabel.NSFW && nsfwScore == 0 { nsfwScore = 100 } diff --git a/internal/entity/photo_label.go b/internal/entity/photo_label.go index 27f142964..9c7bec4d3 100644 --- a/internal/entity/photo_label.go +++ b/internal/entity/photo_label.go @@ -42,7 +42,7 @@ func NewPhotoLabel(photoID, labelID uint, uncertainty int, source string) *Photo } // Updates mutates multiple columns in the database and clears cached copies. -func (m *PhotoLabel) Updates(values interface{}) error { +func (m *PhotoLabel) Updates(values any) error { if m == nil { return errors.New("photo label must not be nil - you may have found a bug") } else if !m.HasID() { @@ -58,7 +58,7 @@ func (m *PhotoLabel) Updates(values interface{}) error { } // Update mutates a single column in the database and clears cached copies. -func (m *PhotoLabel) Update(attr string, value interface{}) error { +func (m *PhotoLabel) Update(attr string, value any) error { if m == nil { return errors.New("photo label must not be nil - you may have found a bug") } else if !m.HasID() { diff --git a/internal/entity/photo_test.go b/internal/entity/photo_test.go index f8d2eb176..edb5bac69 100644 --- a/internal/entity/photo_test.go +++ b/internal/entity/photo_test.go @@ -475,7 +475,6 @@ func TestPhoto_ShouldGenerateCaption(t *testing.T) { } for _, tc := range ctx { - tc := tc t.Run(tc.name, func(t *testing.T) { result := tc.photo.ShouldGenerateCaption(tc.source, tc.force) assert.Equal(t, tc.expect, result) diff --git a/internal/entity/query/faces.go b/internal/entity/query/faces.go index 096bda7e1..64488c9b7 100644 --- a/internal/entity/query/faces.go +++ b/internal/entity/query/faces.go @@ -188,11 +188,7 @@ func PurgeOrphanFaces(faceIds []string, ignored bool) (affected int, err error) batchSize := BatchSize() for i := 0; i < len(faceIds); i += batchSize { - j := i + batchSize - - if j > len(faceIds) { - j = len(faceIds) - } + j := min(i+batchSize, len(faceIds)) // Next batch. ids := faceIds[i:j] diff --git a/internal/entity/query/photo_test.go b/internal/entity/query/photo_test.go index 05729d23c..f35c81835 100644 --- a/internal/entity/query/photo_test.go +++ b/internal/entity/query/photo_test.go @@ -175,7 +175,7 @@ func TestFlagHiddenPhotos(t *testing.T) { t.Run("SuccessWith1000", func(t *testing.T) { var checkedTime = time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC) // Load 1000 photos that need to be hidden - for i := 0; i < 1000; i++ { + for range 1000 { newPhoto := entity.Photo{ //JPG, Geo from metadata, indexed //ID: 1000049, PhotoUID: rnd.GenerateUID(entity.PhotoUID), diff --git a/internal/entity/search/albums_results.go b/internal/entity/search/albums_results.go index a8b2e7e59..2dd48ab50 100644 --- a/internal/entity/search/albums_results.go +++ b/internal/entity/search/albums_results.go @@ -34,7 +34,7 @@ type Album struct { LinkCount int `json:"LinkCount"` CreatedAt time.Time `json:"CreatedAt"` UpdatedAt time.Time `json:"UpdatedAt"` - DeletedAt time.Time `json:"DeletedAt,omitempty"` + DeletedAt time.Time `json:"DeletedAt"` } type AlbumResults []Album diff --git a/internal/entity/search/conditions.go b/internal/entity/search/conditions.go index 372f204a3..312447a16 100644 --- a/internal/entity/search/conditions.go +++ b/internal/entity/search/conditions.go @@ -184,7 +184,7 @@ func AnySlug(col, search, sep string) (where string) { var wheres []string var words []string - for _, w := range strings.Split(search, sep) { + for w := range strings.SplitSeq(search, sep) { w = strings.TrimSpace(w) words = append(words, txt.Slug(w)) @@ -225,7 +225,7 @@ func AnyInt(col, numbers, sep string, min, max int) (where string) { var matches []int var wheres []string - for _, n := range strings.Split(numbers, sep) { + for n := range strings.SplitSeq(numbers, sep) { i := txt.Int(n) if i == 0 || i < min || i > max { @@ -249,19 +249,19 @@ func AnyInt(col, numbers, sep string, min, max int) (where string) { // OrLike prepares a parameterised OR/LIKE clause for a single column. Star (* ) // wildcards are mapped to SQL percent wildcards before returning the query and // bind values. -func OrLike(col, s string) (where string, values []interface{}) { +func OrLike(col, s string) (where string, values []any) { if txt.Empty(col) || txt.Empty(s) { - return "", []interface{}{} + return "", []any{} } s = strings.ReplaceAll(s, "*", "%") s = strings.ReplaceAll(s, "%%", "%") terms := txt.UnTrimmedSplitWithEscape(s, txt.OrRune, txt.EscapeRune) - values = make([]interface{}, len(terms)) + values = make([]any, len(terms)) if l := len(terms); l == 0 { - return "", []interface{}{} + return "", []any{} } else if l == 1 { values[0] = terms[0] } else { @@ -279,9 +279,9 @@ func OrLike(col, s string) (where string, values []interface{}) { // OrLikeCols behaves like OrLike but fans out the same search terms across // multiple columns, preserving the order of values so callers can feed them to // database/sql. -func OrLikeCols(cols []string, s string) (where string, values []interface{}) { +func OrLikeCols(cols []string, s string) (where string, values []any) { if len(cols) == 0 || txt.Empty(s) { - return "", []interface{}{} + return "", []any{} } s = strings.ReplaceAll(s, "*", "%") @@ -290,10 +290,10 @@ func OrLikeCols(cols []string, s string) (where string, values []interface{}) { terms := txt.UnTrimmedSplitWithEscape(s, txt.OrRune, txt.EscapeRune) if len(terms) == 0 { - return "", []interface{}{} + return "", []any{} } - values = make([]interface{}, len(terms)*len(cols)) + values = make([]any, len(terms)*len(cols)) for j := range terms { for i := range cols { diff --git a/internal/entity/search/conditions_test.go b/internal/entity/search/conditions_test.go index c387ec138..ae74407ed 100644 --- a/internal/entity/search/conditions_test.go +++ b/internal/entity/search/conditions_test.go @@ -322,31 +322,31 @@ func TestOrLike(t *testing.T) { where, values := OrLike("k.keyword", "") assert.Equal(t, "", where) - assert.Equal(t, []interface{}{}, values) + assert.Equal(t, []any{}, values) }) t.Run("OneTerm", func(t *testing.T) { where, values := OrLike("k.keyword", "bar") assert.Equal(t, "k.keyword LIKE ?", where) - assert.Equal(t, []interface{}{"bar"}, values) + assert.Equal(t, []any{"bar"}, values) }) t.Run("TwoTerms", func(t *testing.T) { where, values := OrLike("k.keyword", "foo*%|bar") assert.Equal(t, "k.keyword LIKE ? OR k.keyword LIKE ?", where) - assert.Equal(t, []interface{}{"foo%", "bar"}, values) + assert.Equal(t, []any{"foo%", "bar"}, values) }) t.Run("OneFilename", func(t *testing.T) { where, values := OrLike("files.file_name", " 2790/07/27900704_070228_D6D51B6C.jpg") assert.Equal(t, "files.file_name LIKE ?", where) - assert.Equal(t, []interface{}{" 2790/07/27900704_070228_D6D51B6C.jpg"}, values) + assert.Equal(t, []any{" 2790/07/27900704_070228_D6D51B6C.jpg"}, values) }) t.Run("TwoFilenames", func(t *testing.T) { where, values := OrLike("files.file_name", "1990*|2790/07/27900704_070228_D6D51B6C.jpg") assert.Equal(t, "files.file_name LIKE ? OR files.file_name LIKE ?", where) - assert.Equal(t, []interface{}{"1990%", "2790/07/27900704_070228_D6D51B6C.jpg"}, values) + assert.Equal(t, []any{"1990%", "2790/07/27900704_070228_D6D51B6C.jpg"}, values) }) } @@ -355,55 +355,55 @@ func TestOrLikeCols(t *testing.T) { where, values := OrLikeCols([]string{"k.keyword", "p.photo_caption"}, "") assert.Equal(t, "", where) - assert.Equal(t, []interface{}{}, values) + assert.Equal(t, []any{}, values) }) t.Run("OneTerm", func(t *testing.T) { where, values := OrLikeCols([]string{"k.keyword", "p.photo_caption"}, "bar") assert.Equal(t, "k.keyword LIKE ? OR p.photo_caption LIKE ?", where) - assert.Equal(t, []interface{}{"bar", "bar"}, values) + assert.Equal(t, []any{"bar", "bar"}, values) }) t.Run("TwoTerms", func(t *testing.T) { where, values := OrLikeCols([]string{"k.keyword", "p.photo_caption"}, "foo*%|bar") assert.Equal(t, "k.keyword LIKE ? OR k.keyword LIKE ? OR p.photo_caption LIKE ? OR p.photo_caption LIKE ?", where) - assert.Equal(t, []interface{}{"foo%", "bar", "foo%", "bar"}, values) + assert.Equal(t, []any{"foo%", "bar", "foo%", "bar"}, values) }) t.Run("OneTermEscaped", func(t *testing.T) { where, values := OrLikeCols([]string{"k.keyword", "p.photo_caption"}, "\\|bar") assert.Equal(t, "k.keyword LIKE ? OR p.photo_caption LIKE ?", where) - assert.Equal(t, []interface{}{"|bar", "|bar"}, values) + assert.Equal(t, []any{"|bar", "|bar"}, values) }) t.Run("TwoTermsEscaped", func(t *testing.T) { where, values := OrLikeCols([]string{"k.keyword", "p.photo_caption"}, "foo*%|\\|bar") assert.Equal(t, "k.keyword LIKE ? OR k.keyword LIKE ? OR p.photo_caption LIKE ? OR p.photo_caption LIKE ?", where) - assert.Equal(t, []interface{}{"foo%", "|bar", "foo%", "|bar"}, values) + assert.Equal(t, []any{"foo%", "|bar", "foo%", "|bar"}, values) }) t.Run("OneFilename", func(t *testing.T) { where, values := OrLikeCols([]string{"files.file_name"}, " 2790/07/27900704_070228_D6D51B6C.jpg") assert.Equal(t, "files.file_name LIKE ?", where) - assert.Equal(t, []interface{}{" 2790/07/27900704_070228_D6D51B6C.jpg"}, values) + assert.Equal(t, []any{" 2790/07/27900704_070228_D6D51B6C.jpg"}, values) }) t.Run("TwoFilenames", func(t *testing.T) { where, values := OrLikeCols([]string{"files.file_name", "photos.photo_name"}, "1990*|2790/07/27900704_070228_D6D51B6C.jpg") assert.Equal(t, "files.file_name LIKE ? OR files.file_name LIKE ? OR photos.photo_name LIKE ? OR photos.photo_name LIKE ?", where) - assert.Equal(t, []interface{}{"1990%", "2790/07/27900704_070228_D6D51B6C.jpg", "1990%", "2790/07/27900704_070228_D6D51B6C.jpg"}, values) + assert.Equal(t, []any{"1990%", "2790/07/27900704_070228_D6D51B6C.jpg", "1990%", "2790/07/27900704_070228_D6D51B6C.jpg"}, values) }) t.Run("OneFilenameEscaped", func(t *testing.T) { where, values := OrLikeCols([]string{"files.file_name"}, " 2790/07/27900704_070228_D6D\\|51B6C.jpg") assert.Equal(t, "files.file_name LIKE ?", where) - assert.Equal(t, []interface{}{" 2790/07/27900704_070228_D6D|51B6C.jpg"}, values) + assert.Equal(t, []any{" 2790/07/27900704_070228_D6D|51B6C.jpg"}, values) }) t.Run("TwoFilenamesEscaped", func(t *testing.T) { where, values := OrLikeCols([]string{"files.file_name", "photos.photo_name"}, "1990*|2790/07/27900704_070228_D6D\\|51B6C.jpg") assert.Equal(t, "files.file_name LIKE ? OR files.file_name LIKE ? OR photos.photo_name LIKE ? OR photos.photo_name LIKE ?", where) - assert.Equal(t, []interface{}{"1990%", "2790/07/27900704_070228_D6D|51B6C.jpg", "1990%", "2790/07/27900704_070228_D6D|51B6C.jpg"}, values) + assert.Equal(t, []any{"1990%", "2790/07/27900704_070228_D6D|51B6C.jpg", "1990%", "2790/07/27900704_070228_D6D|51B6C.jpg"}, values) }) } diff --git a/internal/entity/search/labels_results.go b/internal/entity/search/labels_results.go index 9f8848ccd..be144bf04 100644 --- a/internal/entity/search/labels_results.go +++ b/internal/entity/search/labels_results.go @@ -21,5 +21,5 @@ type Label struct { ThumbSrc string `json:"ThumbSrc,omitempty"` CreatedAt time.Time `json:"CreatedAt"` UpdatedAt time.Time `json:"UpdatedAt"` - DeletedAt time.Time `json:"DeletedAt,omitempty"` + DeletedAt time.Time `json:"DeletedAt"` } diff --git a/internal/entity/search/photos_results.go b/internal/entity/search/photos_results.go index 3bf48b608..55413e5fe 100644 --- a/internal/entity/search/photos_results.go +++ b/internal/entity/search/photos_results.go @@ -104,8 +104,8 @@ type Photo struct { Merged bool `json:"Merged" select:"-"` CreatedAt time.Time `json:"CreatedAt" select:"photos.created_at"` UpdatedAt time.Time `json:"UpdatedAt" select:"photos.updated_at"` - EditedAt time.Time `json:"EditedAt,omitempty" select:"photos.edited_at"` - CheckedAt time.Time `json:"CheckedAt,omitempty" select:"photos.checked_at"` + EditedAt time.Time `json:"EditedAt" select:"photos.edited_at"` + CheckedAt time.Time `json:"CheckedAt" select:"photos.checked_at"` DeletedAt *time.Time `json:"DeletedAt,omitempty" select:"photos.deleted_at"` // Additional information from the details table. diff --git a/internal/entity/search/select.go b/internal/entity/search/select.go index ec726811e..332dd1707 100644 --- a/internal/entity/search/select.go +++ b/internal/entity/search/select.go @@ -11,15 +11,15 @@ import ( type Cols []string // SelectString returns the columns for a search result struct as a string. -func SelectString(f interface{}, tags []string) string { +func SelectString(f any, tags []string) string { return strings.Join(SelectCols(f, tags), ", ") } // SelectCols returns the columns for a search result struct. -func SelectCols(f interface{}, tags []string) Cols { +func SelectCols(f any, tags []string) Cols { v := reflect.ValueOf(f) - if v.Kind() == reflect.Ptr { + if v.Kind() == reflect.Pointer { v = v.Elem() } diff --git a/internal/entity/service.go b/internal/entity/service.go index d50cf1b8f..ca396b0db 100644 --- a/internal/entity/service.go +++ b/internal/entity/service.go @@ -214,12 +214,12 @@ func (m *Service) Directories() (result fs.FileInfos, err error) { } // Updates multiple columns in the database. -func (m *Service) Updates(values interface{}) error { +func (m *Service) Updates(values any) error { return UnscopedDb().Model(m).UpdateColumns(values).Error } // Update a column in the database. -func (m *Service) Update(attr string, value interface{}) error { +func (m *Service) Update(attr string, value any) error { return UnscopedDb().Model(m).UpdateColumn(attr, value).Error } diff --git a/internal/entity/subject.go b/internal/entity/subject.go index 531213ce2..0ab752299 100644 --- a/internal/entity/subject.go +++ b/internal/entity/subject.go @@ -192,12 +192,12 @@ func (m *Subject) Restore() error { } // Update updates an entity value in the database. -func (m *Subject) Update(attr string, value interface{}) error { +func (m *Subject) Update(attr string, value any) error { return UnscopedDb().Model(m).UpdateColumn(attr, value).Error } // Updates multiple values in the database. -func (m *Subject) Updates(values interface{}) error { +func (m *Subject) Updates(values any) error { return UnscopedDb().Model(m).Updates(values).Error } diff --git a/internal/event/audit.go b/internal/event/audit.go index e8ae96c1c..41ca59bc5 100644 --- a/internal/event/audit.go +++ b/internal/event/audit.go @@ -13,7 +13,7 @@ var AuditLog Logger var AuditPrefix = "audit: " // Audit optionally reports security-relevant events. -func Audit(level logrus.Level, ev []string, args ...interface{}) { +func Audit(level logrus.Level, ev []string, args ...any) { // Skip if empty. if len(ev) == 0 { return @@ -41,21 +41,21 @@ func Audit(level logrus.Level, ev []string, args ...interface{}) { } // AuditDebug records an audit entry at debug level. -func AuditDebug(ev []string, args ...interface{}) { +func AuditDebug(ev []string, args ...any) { Audit(logrus.DebugLevel, ev, args...) } // AuditInfo records an audit entry at info level. -func AuditInfo(ev []string, args ...interface{}) { +func AuditInfo(ev []string, args ...any) { Audit(logrus.InfoLevel, ev, args...) } // AuditWarn records an audit entry at warning level. -func AuditWarn(ev []string, args ...interface{}) { +func AuditWarn(ev []string, args ...any) { Audit(logrus.WarnLevel, ev, args...) } // AuditErr records an audit entry at error level. -func AuditErr(ev []string, args ...interface{}) { +func AuditErr(ev []string, args ...any) { Audit(logrus.ErrorLevel, ev, args...) } diff --git a/internal/event/format.go b/internal/event/format.go index e377906ae..987f6974d 100644 --- a/internal/event/format.go +++ b/internal/event/format.go @@ -9,6 +9,6 @@ import ( var MessageSep = " › " // Format formats an audit log event. -func Format(ev []string, args ...interface{}) string { +func Format(ev []string, args ...any) string { return fmt.Sprintf(strings.Join(ev, MessageSep), args...) } diff --git a/internal/event/logger.go b/internal/event/logger.go index 72d09a8cb..d91e95a7d 100644 --- a/internal/event/logger.go +++ b/internal/event/logger.go @@ -10,32 +10,32 @@ import ( // Logger is a logrus compatible logger interface. type Logger interface { - WithField(key string, value interface{}) *logrus.Entry + WithField(key string, value any) *logrus.Entry WithFields(fields logrus.Fields) *logrus.Entry WithError(err error) *logrus.Entry WithContext(ctx context.Context) *logrus.Entry WithTime(t time.Time) *logrus.Entry - Logf(level logrus.Level, format string, args ...interface{}) - Tracef(format string, args ...interface{}) - Debugf(format string, args ...interface{}) - Infof(format string, args ...interface{}) - Printf(format string, args ...interface{}) - Warnf(format string, args ...interface{}) - Warningf(format string, args ...interface{}) - Errorf(format string, args ...interface{}) - Fatalf(format string, args ...interface{}) - Panicf(format string, args ...interface{}) - Log(level logrus.Level, args ...interface{}) + Logf(level logrus.Level, format string, args ...any) + Tracef(format string, args ...any) + Debugf(format string, args ...any) + Infof(format string, args ...any) + Printf(format string, args ...any) + Warnf(format string, args ...any) + Warningf(format string, args ...any) + Errorf(format string, args ...any) + Fatalf(format string, args ...any) + Panicf(format string, args ...any) + Log(level logrus.Level, args ...any) LogFn(level logrus.Level, fn logrus.LogFunction) - Trace(args ...interface{}) - Debug(args ...interface{}) - Info(args ...interface{}) - Print(args ...interface{}) - Warn(args ...interface{}) - Warning(args ...interface{}) - Error(args ...interface{}) - Fatal(args ...interface{}) - Panic(args ...interface{}) + Trace(args ...any) + Debug(args ...any) + Info(args ...any) + Print(args ...any) + Warn(args ...any) + Warning(args ...any) + Error(args ...any) + Fatal(args ...any) + Panic(args ...any) TraceFn(fn logrus.LogFunction) DebugFn(fn logrus.LogFunction) InfoFn(fn logrus.LogFunction) @@ -45,16 +45,16 @@ type Logger interface { ErrorFn(fn logrus.LogFunction) FatalFn(fn logrus.LogFunction) PanicFn(fn logrus.LogFunction) - Logln(level logrus.Level, args ...interface{}) - Traceln(args ...interface{}) - Debugln(args ...interface{}) - Infoln(args ...interface{}) - Println(args ...interface{}) - Warnln(args ...interface{}) - Warningln(args ...interface{}) - Errorln(args ...interface{}) - Fatalln(args ...interface{}) - Panicln(args ...interface{}) + Logln(level logrus.Level, args ...any) + Traceln(args ...any) + Debugln(args ...any) + Infoln(args ...any) + Println(args ...any) + Warnln(args ...any) + Warningln(args ...any) + Errorln(args ...any) + Fatalln(args ...any) + Panicln(args ...any) Exit(code int) SetNoLock() SetLevel(level logrus.Level) diff --git a/internal/event/publish.go b/internal/event/publish.go index 9ee92f7b1..13980b2f0 100644 --- a/internal/event/publish.go +++ b/internal/event/publish.go @@ -39,21 +39,21 @@ func Warn(msg string) { } // ErrorMsg publishes a localized error notification. -func ErrorMsg(id i18n.Message, params ...interface{}) { +func ErrorMsg(id i18n.Message, params ...any) { Error(i18n.Msg(id, params...)) } // SuccessMsg publishes a localized success notification. -func SuccessMsg(id i18n.Message, params ...interface{}) { +func SuccessMsg(id i18n.Message, params ...any) { Success(i18n.Msg(id, params...)) } // InfoMsg publishes a localized informational notification. -func InfoMsg(id i18n.Message, params ...interface{}) { +func InfoMsg(id i18n.Message, params ...any) { Info(i18n.Msg(id, params...)) } // WarnMsg publishes a localized warning notification. -func WarnMsg(id i18n.Message, params ...interface{}) { +func WarnMsg(id i18n.Message, params ...any) { Warn(i18n.Msg(id, params...)) } diff --git a/internal/event/publish_entities.go b/internal/event/publish_entities.go index b1e3fa8ff..490e6361d 100644 --- a/internal/event/publish_entities.go +++ b/internal/event/publish_entities.go @@ -16,7 +16,7 @@ const ( ) // PublishEntities publishes updated entity data. -func PublishEntities(channel, ev string, entities interface{}) { +func PublishEntities(channel, ev string, entities any) { if channel == "" || ev == "" || entities == nil { return } @@ -29,7 +29,7 @@ func PublishEntities(channel, ev string, entities interface{}) { } // PublishUserEntities publishes updated entity data for a user. -func PublishUserEntities(channel, ev string, entities interface{}, userUid string) { +func PublishUserEntities(channel, ev string, entities any, userUid string) { if userUid == "" { PublishEntities(channel, ev, entities) return @@ -46,26 +46,26 @@ func PublishUserEntities(channel, ev string, entities interface{}, userUid strin } // EntitiesUpdated publishes an update notification for the given channel. -func EntitiesUpdated(channel string, entities interface{}) { +func EntitiesUpdated(channel string, entities any) { PublishEntities(channel, EntityUpdated, entities) } // EntitiesCreated publishes a create notification for the given channel. -func EntitiesCreated(channel string, entities interface{}) { +func EntitiesCreated(channel string, entities any) { PublishEntities(channel, EntityCreated, entities) } // EntitiesDeleted publishes a delete notification for the given channel. -func EntitiesDeleted(channel string, entities interface{}) { +func EntitiesDeleted(channel string, entities any) { PublishEntities(channel, EntityDeleted, entities) } // EntitiesArchived publishes an archive notification for the given channel. -func EntitiesArchived(channel string, entities interface{}) { +func EntitiesArchived(channel string, entities any) { PublishEntities(channel, EntityArchived, entities) } // EntitiesRestored publishes a restore notification for the given channel. -func EntitiesRestored(channel string, entities interface{}) { +func EntitiesRestored(channel string, entities any) { PublishEntities(channel, EntityRestored, entities) } diff --git a/internal/event/system.go b/internal/event/system.go index fc51dd0bc..e4c70f8b5 100644 --- a/internal/event/system.go +++ b/internal/event/system.go @@ -13,7 +13,7 @@ var SystemLog Logger var SystemPrefix = "system: " // System writes a system-level log entry and publishes it to the hub. -func System(level logrus.Level, ev []string, args ...interface{}) { +func System(level logrus.Level, ev []string, args ...any) { if len(ev) == 0 { return } @@ -35,21 +35,21 @@ func System(level logrus.Level, ev []string, args ...interface{}) { } // SystemDebug records a system debug message. -func SystemDebug(ev []string, args ...interface{}) { +func SystemDebug(ev []string, args ...any) { System(logrus.DebugLevel, ev, args...) } // SystemInfo records a system info message. -func SystemInfo(ev []string, args ...interface{}) { +func SystemInfo(ev []string, args ...any) { System(logrus.InfoLevel, ev, args...) } // SystemWarn records a system warning. -func SystemWarn(ev []string, args ...interface{}) { +func SystemWarn(ev []string, args ...any) { System(logrus.WarnLevel, ev, args...) } // SystemError records a system error message. -func SystemError(ev []string, args ...interface{}) { +func SystemError(ev []string, args ...any) { System(logrus.ErrorLevel, ev, args...) } diff --git a/internal/event/system_test.go b/internal/event/system_test.go index 8492440dc..05bcb0bfb 100644 --- a/internal/event/system_test.go +++ b/internal/event/system_test.go @@ -19,7 +19,7 @@ type systemTestLogger struct { type logEntry struct { level logrus.Level - args []interface{} + args []any } func newSystemTestLogger() *systemTestLogger { @@ -29,11 +29,11 @@ func newSystemTestLogger() *systemTestLogger { return &systemTestLogger{Logger: logger} } -func (l *systemTestLogger) Log(level logrus.Level, args ...interface{}) { +func (l *systemTestLogger) Log(level logrus.Level, args ...any) { l.mu.Lock() defer l.mu.Unlock() - entryArgs := append([]interface{}(nil), args...) + entryArgs := append([]any(nil), args...) l.entries = append(l.entries, logEntry{level: level, args: entryArgs}) } @@ -59,12 +59,12 @@ func TestSystemLoggingFunctions(t *testing.T) { tests := []struct { name string level logrus.Level - call func(ev []string, args ...interface{}) + call func(ev []string, args ...any) }{ - {name: "Info", level: logrus.InfoLevel, call: func(ev []string, args ...interface{}) { SystemInfo(ev, args...) }}, - {name: "Warn", level: logrus.WarnLevel, call: func(ev []string, args ...interface{}) { SystemWarn(ev, args...) }}, - {name: "Debug", level: logrus.DebugLevel, call: func(ev []string, args ...interface{}) { SystemDebug(ev, args...) }}, - {name: "Error", level: logrus.ErrorLevel, call: func(ev []string, args ...interface{}) { SystemError(ev, args...) }}, + {name: "Info", level: logrus.InfoLevel, call: func(ev []string, args ...any) { SystemInfo(ev, args...) }}, + {name: "Warn", level: logrus.WarnLevel, call: func(ev []string, args ...any) { SystemWarn(ev, args...) }}, + {name: "Debug", level: logrus.DebugLevel, call: func(ev []string, args ...any) { SystemDebug(ev, args...) }}, + {name: "Error", level: logrus.ErrorLevel, call: func(ev []string, args ...any) { SystemError(ev, args...) }}, } for _, tt := range tests { @@ -75,7 +75,7 @@ func TestSystemLoggingFunctions(t *testing.T) { t.Cleanup(func() { SystemLog = orig }) events := []string{"cleanup %s", "finished"} - args := []interface{}{"cache"} + args := []any{"cache"} expectedMessage := Format(events, args...) topic := "system.log." + tt.level.String() diff --git a/internal/form/album.go b/internal/form/album.go index 3d1fe1aa4..3e2115ba9 100644 --- a/internal/form/album.go +++ b/internal/form/album.go @@ -22,7 +22,7 @@ type Album struct { } // NewAlbum creates a new form struct based on the interface values. -func NewAlbum(m interface{}) (*Album, error) { +func NewAlbum(m any) (*Album, error) { frm := &Album{} err := deepcopier.Copy(m).To(frm) diff --git a/internal/form/face.go b/internal/form/face.go index 267a4d81e..414b50442 100644 --- a/internal/form/face.go +++ b/internal/form/face.go @@ -9,7 +9,7 @@ type Face struct { } // NewFace copies values from an arbitrary model into a Face form. -func NewFace(m interface{}) (f Face, err error) { +func NewFace(m any) (f Face, err error) { err = deepcopier.Copy(m).To(&f) return f, err diff --git a/internal/form/feedback.go b/internal/form/feedback.go index 615031348..27653465e 100644 --- a/internal/form/feedback.go +++ b/internal/form/feedback.go @@ -18,7 +18,7 @@ func (f Feedback) Empty() bool { } // NewFeedback copies values from an arbitrary model into a Feedback form. -func NewFeedback(m interface{}) (f Feedback, err error) { +func NewFeedback(m any) (f Feedback, err error) { err = deepcopier.Copy(m).To(&f) return f, err diff --git a/internal/form/file.go b/internal/form/file.go index c73274a57..b195a8952 100644 --- a/internal/form/file.go +++ b/internal/form/file.go @@ -17,7 +17,7 @@ func (f *File) Orientation() int { } // NewFile copies values from an arbitrary model into a File form. -func NewFile(m interface{}) (f File, err error) { +func NewFile(m any) (f File, err error) { err = deepcopier.Copy(m).To(&f) return f, err diff --git a/internal/form/folder.go b/internal/form/folder.go index d0da3edb8..69b50a269 100644 --- a/internal/form/folder.go +++ b/internal/form/folder.go @@ -21,7 +21,7 @@ type Folder struct { } // NewFolder copies values from an arbitrary model into a Folder form. -func NewFolder(m interface{}) (f Folder, err error) { +func NewFolder(m any) (f Folder, err error) { err = deepcopier.Copy(m).To(&f) return f, err diff --git a/internal/form/form_report.go b/internal/form/form_report.go index 3a714b750..ea582e14e 100644 --- a/internal/form/form_report.go +++ b/internal/form/form_report.go @@ -9,12 +9,12 @@ import ( ) // Report returns form fields as table rows for reports. -func Report(f interface{}) (rows [][]string, cols []string) { +func Report(f any) (rows [][]string, cols []string) { cols = []string{"Filter", "Type", "Examples", "Notes"} v := reflect.ValueOf(f) - if v.Kind() == reflect.Ptr { + if v.Kind() == reflect.Pointer { v = v.Elem() } diff --git a/internal/form/json.go b/internal/form/json.go index e7e93af27..07a69690e 100644 --- a/internal/form/json.go +++ b/internal/form/json.go @@ -7,13 +7,13 @@ import ( ) // AsJson returns the form data as a JSON string or an empty string in case of error. -func AsJson(frm interface{}) string { +func AsJson(frm any) string { s, _ := json.Marshal(frm) return string(s) } // AsReader returns the form data as io.Reader, e.g. for use in tests. -func AsReader(frm interface{}) io.Reader { +func AsReader(frm any) io.Reader { return strings.NewReader(AsJson(frm)) } diff --git a/internal/form/label.go b/internal/form/label.go index c20a5b3ad..705110cf9 100644 --- a/internal/form/label.go +++ b/internal/form/label.go @@ -21,7 +21,7 @@ type Label struct { } // NewLabel creates a new form struct based on the interface values. -func NewLabel(m interface{}) (*Label, error) { +func NewLabel(m any) (*Label, error) { frm := &Label{} err := deepcopier.Copy(m).To(frm) return frm, err diff --git a/internal/form/marker.go b/internal/form/marker.go index f1530c1d2..a0af67e4b 100644 --- a/internal/form/marker.go +++ b/internal/form/marker.go @@ -24,7 +24,7 @@ type Marker struct { } // NewMarker creates a new form initialized with model values. -func NewMarker(m interface{}) (f Marker, err error) { +func NewMarker(m any) (f Marker, err error) { err = deepcopier.Copy(m).To(&f) return f, err diff --git a/internal/form/photo.go b/internal/form/photo.go index 0a2b0f096..6a880a565 100644 --- a/internal/form/photo.go +++ b/internal/form/photo.go @@ -65,7 +65,7 @@ type Photo struct { } // NewPhoto creates Photo struct from interface -func NewPhoto(m interface{}) (f Photo, err error) { +func NewPhoto(m any) (f Photo, err error) { err = deepcopier.Copy(m).To(&f) return f, err diff --git a/internal/form/serialize.go b/internal/form/serialize.go index dfc1adcb4..7e0f07177 100644 --- a/internal/form/serialize.go +++ b/internal/form/serialize.go @@ -13,10 +13,10 @@ import ( ) // Serialize returns a string containing all non-empty fields and values of a struct. -func Serialize(f interface{}, all bool) string { +func Serialize(f any, all bool) string { v := reflect.ValueOf(f) - if v.Kind() == reflect.Ptr { + if v.Kind() == reflect.Pointer { v = v.Elem() } diff --git a/internal/form/service.go b/internal/form/service.go index 1f9dbe7f1..82f85a58b 100644 --- a/internal/form/service.go +++ b/internal/form/service.go @@ -32,7 +32,7 @@ type Service struct { } // NewService creates a new service form. -func NewService(m interface{}) (f Service, err error) { +func NewService(m any) (f Service, err error) { err = deepcopier.Copy(m).To(&f) return f, err diff --git a/internal/form/subject.go b/internal/form/subject.go index 28b65afc4..942aa104d 100644 --- a/internal/form/subject.go +++ b/internal/form/subject.go @@ -18,7 +18,7 @@ type Subject struct { } // NewSubject copies values from an arbitrary model into a Subject form. -func NewSubject(m interface{}) (*Subject, error) { +func NewSubject(m any) (*Subject, error) { frm := &Subject{} err := deepcopier.Copy(m).To(frm) diff --git a/internal/meta/report.go b/internal/meta/report.go index 0de4e9d4c..dae7d8389 100644 --- a/internal/meta/report.go +++ b/internal/meta/report.go @@ -11,12 +11,12 @@ import ( ) // Report returns form fields as table rows for reports. -func Report(f interface{}) (rows [][]string, cols []string) { +func Report(f any) (rows [][]string, cols []string) { cols = []string{"Field", "Type", "Exiftool", "Adobe XMP", "DCMI"} v := reflect.ValueOf(f) - if v.Kind() == reflect.Ptr { + if v.Kind() == reflect.Pointer { v = v.Elem() } diff --git a/internal/meta/xmp_document.go b/internal/meta/xmp_document.go index da938a975..65c289558 100644 --- a/internal/meta/xmp_document.go +++ b/internal/meta/xmp_document.go @@ -11,7 +11,7 @@ import ( // XmpDocument represents an XMP sidecar file. type XmpDocument struct { - XMLName xml.Name `xml:"xmpmeta" json:"xmpmeta,omitempty"` + XMLName xml.Name `xml:"xmpmeta" json:"xmpmeta"` Text string `xml:",chardata" json:"text,omitempty"` X string `xml:"x,attr" json:"x,omitempty"` Xmptk string `xml:"xmptk,attr" json:"xmptk,omitempty"` @@ -54,16 +54,16 @@ type XmpDocument struct { Li struct { Text string `xml:",chardata" json:"text,omitempty"` // Night Shift / Berlin / 20... Lang string `xml:"lang,attr" json:"lang,omitempty"` - } `xml:"li" json:"li,omitempty"` - } `xml:"Alt" json:"alt,omitempty"` - } `xml:"title" json:"title,omitempty"` + } `xml:"li" json:"li"` + } `xml:"Alt" json:"alt"` + } `xml:"title" json:"title"` Creator struct { Text string `xml:",chardata" json:"text,omitempty"` Seq struct { Text string `xml:",chardata" json:"text,omitempty"` Li string `xml:"li"` // Michael Mayer - } `xml:"Seq" json:"seq,omitempty"` - } `xml:"creator" json:"creator,omitempty"` + } `xml:"Seq" json:"seq"` + } `xml:"creator" json:"creator"` Description struct { Text string `xml:",chardata" json:"text,omitempty"` Alt struct { @@ -71,20 +71,20 @@ type XmpDocument struct { Li struct { Text string `xml:",chardata" json:"text,omitempty"` // Example file for developm... Lang string `xml:"lang,attr" json:"lang,omitempty"` - } `xml:"li" json:"li,omitempty"` - } `xml:"Alt" json:"alt,omitempty"` - } `xml:"description" json:"description,omitempty"` + } `xml:"li" json:"li"` + } `xml:"Alt" json:"alt"` + } `xml:"description" json:"description"` Subject struct { Text string `xml:",chardata" json:"text,omitempty"` Bag struct { Text string `xml:",chardata" json:"text,omitempty"` Li []string `xml:"li"` // desk, coffee, computer - } `xml:"Bag" json:"bag,omitempty"` + } `xml:"Bag" json:"bag"` Seq struct { Text string `xml:",chardata" json:"text,omitempty"` Li []string `xml:"li"` // desk, coffee, computer - } `xml:"Seq" json:"seq,omitempty"` - } `xml:"subject" json:"subject,omitempty"` + } `xml:"Seq" json:"seq"` + } `xml:"subject" json:"subject"` Rights struct { Text string `xml:",chardata" json:"text,omitempty"` Alt struct { @@ -92,9 +92,9 @@ type XmpDocument struct { Li struct { Text string `xml:",chardata" json:"text,omitempty"` // This is an (edited) legal... Lang string `xml:"lang,attr" json:"lang,omitempty"` - } `xml:"li" json:"li,omitempty"` - } `xml:"Alt" json:"alt,omitempty"` - } `xml:"rights" json:"rights,omitempty"` + } `xml:"li" json:"li"` + } `xml:"Alt" json:"alt"` + } `xml:"rights" json:"rights"` ImageWidth string `xml:"ImageWidth"` // 3648 ImageLength string `xml:"ImageLength"` // 2736 BitsPerSample struct { @@ -102,8 +102,8 @@ type XmpDocument struct { Seq struct { Text string `xml:",chardata" json:"text,omitempty"` Li []string `xml:"li"` // 8 - } `xml:"Seq" json:"seq,omitempty"` - } `xml:"BitsPerSample" json:"bitspersample,omitempty"` + } `xml:"Seq" json:"seq"` + } `xml:"BitsPerSample" json:"bitspersample"` PhotometricInterpretation string `xml:"PhotometricInterpretation"` // 2 Orientation string `xml:"Orientation"` // 0 SamplesPerPixel string `xml:"SamplesPerPixel"` // 3 @@ -121,8 +121,8 @@ type XmpDocument struct { Seq struct { Text string `xml:",chardata" json:"text,omitempty"` Li []string `xml:"li"` // 1, 2, 3, 0 - } `xml:"Seq" json:"seq,omitempty"` - } `xml:"ComponentsConfiguration" json:"componentsconfiguration,omitempty"` + } `xml:"Seq" json:"seq"` + } `xml:"ComponentsConfiguration" json:"componentsconfiguration"` CompressedBitsPerPixel string `xml:"CompressedBitsPerPixel"` // 95/100 PixelXDimension string `xml:"PixelXDimension"` // 3648 PixelYDimension string `xml:"PixelYDimension"` // 2736 @@ -135,8 +135,8 @@ type XmpDocument struct { Seq struct { Text string `xml:",chardata" json:"text,omitempty"` Li string `xml:"li"` // 200 - } `xml:"Seq" json:"seq,omitempty"` - } `xml:"ISOSpeedRatings" json:"isospeedratings,omitempty"` + } `xml:"Seq" json:"seq"` + } `xml:"ISOSpeedRatings" json:"isospeedratings"` ShutterSpeedValue string `xml:"ShutterSpeedValue"` // 298973/10000 ApertureValue string `xml:"ApertureValue"` // 1695994/1000000 BrightnessValue string `xml:"BrightnessValue"` // 0/1 @@ -152,7 +152,7 @@ type XmpDocument struct { Mode string `xml:"Mode"` // 0 Function string `xml:"Function"` // False RedEyeMode string `xml:"RedEyeMode"` // False - } `xml:"Flash" json:"flash,omitempty"` + } `xml:"Flash" json:"flash"` FocalLength string `xml:"FocalLength"` // 5580/1000 SensingMethod string `xml:"SensingMethod"` // 2 FileSource string `xml:"FileSource"` // 3 @@ -189,16 +189,16 @@ type XmpDocument struct { CiTelWork string `xml:"CiTelWork"` // +49123456789 CiEmailWork string `xml:"CiEmailWork"` // hello@photoprism.org CiUrlWork string `xml:"CiUrlWork"` // https://photoprism.org/ - } `xml:"CreatorContactInfo" json:"creatorcontactinfo,omitempty"` + } `xml:"CreatorContactInfo" json:"creatorcontactinfo"` PersonInImage struct { Text string `xml:",chardata" json:"text,omitempty"` Bag struct { Text string `xml:",chardata" json:"text,omitempty"` Li string `xml:"li"` // Gopher - } `xml:"Bag" json:"bag,omitempty"` - } `xml:"PersonInImage" json:"personinimage,omitempty"` - } `xml:"Description" json:"description,omitempty"` - } `xml:"RDF" json:"rdf,omitempty"` + } `xml:"Bag" json:"bag"` + } `xml:"PersonInImage" json:"personinimage"` + } `xml:"Description" json:"description"` + } `xml:"RDF" json:"rdf"` } // Load parses an XMP file and populates document values with its contents. diff --git a/internal/photoprism/batch/apply_labels.go b/internal/photoprism/batch/apply_labels.go index 651997cb2..73fe0ec17 100644 --- a/internal/photoprism/batch/apply_labels.go +++ b/internal/photoprism/batch/apply_labels.go @@ -379,7 +379,7 @@ func deletePhotoLabel(pl *entity.PhotoLabel) error { // withDeadlockRetry executes fn and retries a few times if the database reports // a deadlock, helping batch edits succeed without surfacing errors to users. func withDeadlockRetry(action string, fn func() error) (err error) { - for attempt := 0; attempt < deadlockRetryAttempts; attempt++ { + for attempt := range deadlockRetryAttempts { err = fn() if err == nil { return nil diff --git a/internal/photoprism/batch/apply_labels_test.go b/internal/photoprism/batch/apply_labels_test.go index 924d2ec76..01f0e9fdd 100644 --- a/internal/photoprism/batch/apply_labels_test.go +++ b/internal/photoprism/batch/apply_labels_test.go @@ -1354,7 +1354,6 @@ func TestDetermineLabelRemovalAction(t *testing.T) { } for _, tc := range tests { - tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() @@ -1390,7 +1389,6 @@ func TestLabelRemovalActionString(t *testing.T) { } for _, tc := range tests { - tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() diff --git a/internal/photoprism/batch/photos.go b/internal/photoprism/batch/photos.go index 3a34bb392..e2b11cac5 100644 --- a/internal/photoprism/batch/photos.go +++ b/internal/photoprism/batch/photos.go @@ -15,37 +15,37 @@ import ( // Once the frontend adds those controls, extend ConvertToPhotoForm and AddLabels // to persist the values before removing this note. type PhotosForm struct { - PhotoType String `json:"Type,omitempty"` - PhotoTitle String `json:"Title,omitempty"` - PhotoCaption String `json:"Caption,omitempty"` - TakenAt Time `json:"TakenAt,omitempty"` - TakenAtLocal Time `json:"TakenAtLocal,omitempty"` - PhotoDay Int `json:"Day,omitempty"` - PhotoMonth Int `json:"Month,omitempty"` - PhotoYear Int `json:"Year,omitempty"` - TimeZone String `json:"TimeZone,omitempty"` - PhotoCountry String `json:"Country,omitempty"` - PhotoAltitude Int `json:"Altitude,omitempty"` - PhotoLat Float64 `json:"Lat,omitempty"` - PhotoLng Float64 `json:"Lng,omitempty"` - PhotoIso Int `json:"Iso,omitempty"` - PhotoFocalLength Int `json:"FocalLength,omitempty"` - PhotoFNumber Float32 `json:"FNumber,omitempty"` - PhotoExposure String `json:"Exposure,omitempty"` - PhotoFavorite Bool `json:"Favorite,omitempty"` - PhotoPrivate Bool `json:"Private,omitempty"` - PhotoScan Bool `json:"Scan,omitempty"` - PhotoPanorama Bool `json:"Panorama,omitempty"` - CameraID Int `json:"CameraID,omitempty"` - LensID Int `json:"LensID,omitempty"` - Albums Items `json:"Albums,omitempty"` - Labels Items `json:"Labels,omitempty"` + PhotoType String `json:"Type"` + PhotoTitle String `json:"Title"` + PhotoCaption String `json:"Caption"` + TakenAt Time `json:"TakenAt"` + TakenAtLocal Time `json:"TakenAtLocal"` + PhotoDay Int `json:"Day"` + PhotoMonth Int `json:"Month"` + PhotoYear Int `json:"Year"` + TimeZone String `json:"TimeZone"` + PhotoCountry String `json:"Country"` + PhotoAltitude Int `json:"Altitude"` + PhotoLat Float64 `json:"Lat"` + PhotoLng Float64 `json:"Lng"` + PhotoIso Int `json:"Iso"` + PhotoFocalLength Int `json:"FocalLength"` + PhotoFNumber Float32 `json:"FNumber"` + PhotoExposure String `json:"Exposure"` + PhotoFavorite Bool `json:"Favorite"` + PhotoPrivate Bool `json:"Private"` + PhotoScan Bool `json:"Scan"` + PhotoPanorama Bool `json:"Panorama"` + CameraID Int `json:"CameraID"` + LensID Int `json:"LensID"` + Albums Items `json:"Albums"` + Labels Items `json:"Labels"` - DetailsKeywords String `json:"DetailsKeywords,omitempty"` - DetailsSubject String `json:"DetailsSubject,omitempty"` - DetailsArtist String `json:"DetailsArtist,omitempty"` - DetailsCopyright String `json:"DetailsCopyright,omitempty"` - DetailsLicense String `json:"DetailsLicense,omitempty"` + DetailsKeywords String `json:"DetailsKeywords"` + DetailsSubject String `json:"DetailsSubject"` + DetailsArtist String `json:"DetailsArtist"` + DetailsCopyright String `json:"DetailsCopyright"` + DetailsLicense String `json:"DetailsLicense"` } // NewPhotosForm returns a new batch edit form instance initialized with values from the diff --git a/internal/photoprism/batch/save_photo.go b/internal/photoprism/batch/save_photo.go index 0aa901d30..0a1bfd441 100644 --- a/internal/photoprism/batch/save_photo.go +++ b/internal/photoprism/batch/save_photo.go @@ -139,7 +139,7 @@ func savePhoto(req *PhotoSaveRequest) (bool, error) { } updates := entity.Values{} - addUpdate := func(column string, changed bool, value interface{}) { + addUpdate := func(column string, changed bool, value any) { if changed { updates[column] = value } diff --git a/internal/photoprism/convert.go b/internal/photoprism/convert.go index 360c34cf3..0d456875a 100644 --- a/internal/photoprism/convert.go +++ b/internal/photoprism/convert.go @@ -60,7 +60,7 @@ func (w *Convert) Start(dir string, ext []string, force bool) (err error) { var wg sync.WaitGroup var numWorkers = w.conf.IndexWorkers() wg.Add(numWorkers) - for i := 0; i < numWorkers; i++ { + for range numWorkers { go func() { ConvertWorker(jobs) wg.Done() diff --git a/internal/photoprism/faces_cluster_bench_test.go b/internal/photoprism/faces_cluster_bench_test.go index 24ec5516b..76e57d33b 100644 --- a/internal/photoprism/faces_cluster_bench_test.go +++ b/internal/photoprism/faces_cluster_bench_test.go @@ -23,8 +23,8 @@ func BenchmarkClusterMaterialize(b *testing.B) { guesses := make([]int, total) sizes := make([]int, clusterCount) - for cluster := 0; cluster < clusterCount; cluster++ { - for j := 0; j < embeddingsPerCluster; j++ { + for cluster := range clusterCount { + for j := range embeddingsPerCluster { idx := cluster*embeddingsPerCluster + j guesses[idx] = cluster + 1 sizes[cluster]++ diff --git a/internal/photoprism/faces_match.go b/internal/photoprism/faces_match.go index 747e6a89a..8a821ca57 100644 --- a/internal/photoprism/faces_match.go +++ b/internal/photoprism/faces_match.go @@ -411,11 +411,7 @@ func minMarkerDistance(faceEmb face.Embedding, embeddings face.Embeddings) float func embeddingSignHash(values []float64) uint32 { var hash uint32 - limit := faceIndexHashDims - - if limit > len(values) { - limit = len(values) - } + limit := min(faceIndexHashDims, len(values)) for i := 0; i < limit; i++ { if values[i] >= 0 && i < 32 { @@ -434,11 +430,7 @@ func embeddingSignHashFromEmbeddings(embeddings face.Embeddings) uint32 { return 0 } - dims := faceIndexHashDims - - if dims > len(embeddings[0]) { - dims = len(embeddings[0]) - } + dims := min(faceIndexHashDims, len(embeddings[0])) var sums [faceIndexHashDims]float64 diff --git a/internal/photoprism/faces_match_bench_test.go b/internal/photoprism/faces_match_bench_test.go index d2336fd9e..30183d781 100644 --- a/internal/photoprism/faces_match_bench_test.go +++ b/internal/photoprism/faces_match_bench_test.go @@ -13,7 +13,7 @@ func BenchmarkSelectBestFace(b *testing.B) { faces := make(entity.Faces, 0, candidateCount) - for i := 0; i < candidateCount; i++ { + for range candidateCount { f := entity.NewFace("", entity.SrcAuto, face.RandomEmbeddings(5, face.RegularFace)) faces = append(faces, *f) } @@ -41,7 +41,7 @@ func BenchmarkSelectBestFaceLegacy(b *testing.B) { faces := make(entity.Faces, 0, candidateCount) - for i := 0; i < candidateCount; i++ { + for range candidateCount { f := entity.NewFace("", entity.SrcAuto, face.RandomEmbeddings(5, face.RegularFace)) faces = append(faces, *f) } diff --git a/internal/photoprism/faces_stats.go b/internal/photoprism/faces_stats.go index e25777245..6d718af0b 100644 --- a/internal/photoprism/faces_stats.go +++ b/internal/photoprism/faces_stats.go @@ -18,11 +18,11 @@ func (w *Faces) Stats() (err error) { distMin := make([]float64, samples) distMax := make([]float64, samples) - for i := 0; i < samples; i++ { + for i := range samples { min := -1.0 max := -1.0 - for j := 0; j < samples; j++ { + for j := range samples { if i == j { continue } @@ -62,7 +62,7 @@ func (w *Faces) Stats() (err error) { dist := make(map[string][]float64) - for i := 0; i < samples; i++ { + for i := range samples { f1 := faces[i] e1 := f1.Embedding() @@ -74,7 +74,7 @@ func (w *Faces) Stats() (err error) { max = k[1] } - for j := 0; j < samples; j++ { + for j := range samples { if i == j { continue } diff --git a/internal/photoprism/import.go b/internal/photoprism/import.go index ae20621d2..250f385e0 100644 --- a/internal/photoprism/import.go +++ b/internal/photoprism/import.go @@ -91,7 +91,7 @@ func (imp *Import) Start(opt ImportOptions) fs.Done { var wg sync.WaitGroup var numWorkers = imp.conf.IndexWorkers() wg.Add(numWorkers) - for i := 0; i < numWorkers; i++ { + for range numWorkers { go func() { ImportWorker(jobs) wg.Done() diff --git a/internal/photoprism/index.go b/internal/photoprism/index.go index f79ec5521..11d9eb7c5 100644 --- a/internal/photoprism/index.go +++ b/internal/photoprism/index.go @@ -103,7 +103,7 @@ func (ind *Index) Start(o IndexOptions) (found fs.Done, updated int) { var wg sync.WaitGroup var numWorkers = ind.conf.IndexWorkers() wg.Add(numWorkers) - for i := 0; i < numWorkers; i++ { + for range numWorkers { go func() { IndexWorker(jobs) // HLc wg.Done() diff --git a/internal/photoprism/mediafile.go b/internal/photoprism/mediafile.go index 0a47f7444..7ebcaaa5b 100644 --- a/internal/photoprism/mediafile.go +++ b/internal/photoprism/mediafile.go @@ -14,6 +14,7 @@ import ( "path/filepath" "regexp" "runtime/debug" + "slices" "strings" "sync" "time" @@ -312,8 +313,8 @@ func (m *MediaFile) CanonicalNameDefault() string { func (m *MediaFile) CanonicalNameFromFile() string { basename := filepath.Base(m.FileName()) - if end := strings.Index(basename, "."); end != -1 { - return basename[:end] // Length of canonical name: 16 + 12 + if before, _, ok := strings.Cut(basename, "."); ok { + return before // Length of canonical name: 16 + 12 } return basename @@ -1058,13 +1059,7 @@ func (m *MediaFile) MediaType() media.Type { func (m *MediaFile) HasMediaType(mediaTypes ...media.Type) bool { mediaType := m.MediaType() - for _, t := range mediaTypes { - if mediaType == t { - return true - } - } - - return false + return slices.Contains(mediaTypes, mediaType) } // HasFileType checks if the file has the given file type. diff --git a/internal/photoprism/places.go b/internal/photoprism/places.go index a783928df..d0ebf231d 100644 --- a/internal/photoprism/places.go +++ b/internal/photoprism/places.go @@ -142,7 +142,7 @@ func (w *Places) UpdatePhotos(force bool) (affected int, err error) { log.Infof("index: updating references, titles, and keywords") - for i := 0; i < n; i++ { + for i := range n { if i%10 == 0 { log.Infof("index: updated %s, %s remaining", english.Plural(i, "photo", "photos"), diff --git a/internal/photoprism/thumbs.go b/internal/photoprism/thumbs.go index 39468dbc3..097979bbb 100644 --- a/internal/photoprism/thumbs.go +++ b/internal/photoprism/thumbs.go @@ -77,7 +77,7 @@ func (w *Thumbs) Dir(dir string, force bool) (fs.Done, error) { wg.Add(numWorkers) - for i := 0; i < numWorkers; i++ { + for range numWorkers { go func() { ThumbsWorker(jobs) wg.Done() diff --git a/internal/server/fail.go b/internal/server/fail.go index 3ab9d54af..96beab453 100644 --- a/internal/server/fail.go +++ b/internal/server/fail.go @@ -5,7 +5,7 @@ import ( ) // Fail logs an error and then initiates a server shutdown. -func Fail(err string, params ...interface{}) { +func Fail(err string, params ...any) { if err != "" { log.Errorf(err, params...) } diff --git a/internal/server/limiter/auth_test.go b/internal/server/limiter/auth_test.go index d1f2d2c76..7e81262bc 100644 --- a/internal/server/limiter/auth_test.go +++ b/internal/server/limiter/auth_test.go @@ -10,7 +10,7 @@ import ( func TestAuth(t *testing.T) { clientIp := "192.0.2.42" - for i := 0; i < 59; i++ { + for range 59 { t.Logf("tokens now: %f", Auth.IP(clientIp).TokensAt(time.Now())) assert.True(t, Auth.IP(clientIp).Allow()) } @@ -38,7 +38,7 @@ func TestAuth(t *testing.T) { assert.InEpsilon(t, 10, Auth.IP(clientIp).TokensAt(time.Now().Add(DefaultAuthInterval*10)), 0.1) assert.InEpsilon(t, DefaultAuthLimit, Auth.IP(clientIp).TokensAt(time.Now().Add(DefaultAuthInterval*DefaultAuthLimit*10)), 0.01) - for i := 0; i < 30; i++ { + for range 30 { assert.False(t, Auth.IP(clientIp).Allow()) } diff --git a/internal/server/limiter/limit_test.go b/internal/server/limiter/limit_test.go index 72f97ee3f..bc20a68d9 100644 --- a/internal/server/limiter/limit_test.go +++ b/internal/server/limiter/limit_test.go @@ -13,14 +13,14 @@ func TestNewLimit(t *testing.T) { t.Run("BelowLimit", func(t *testing.T) { // 10 per minute. l := NewLimit(0.166, 10) - for i := 0; i < 9; i++ { + for range 9 { assert.True(t, l.IP(clientIp).Allow()) } }) t.Run("AboveLimit", func(t *testing.T) { // 10 per minute. l := NewLimit(0.166, 10) - for i := 0; i < 10; i++ { + for range 10 { assert.True(t, l.IP(clientIp).Allow()) } assert.False(t, l.IP(clientIp).Allow()) @@ -28,7 +28,7 @@ func TestNewLimit(t *testing.T) { t.Run("MultipleIPs", func(t *testing.T) { // 10 per minute. l := NewLimit(0.166, 10) - for i := 0; i < 100; i++ { + for i := range 100 { assert.True(t, l.IP(fmt.Sprintf("192.0.2.%d", i)).Allow()) } }) @@ -37,17 +37,17 @@ func TestNewLimit(t *testing.T) { l := NewLimit(0.166, 10) // Request counter not increased. - for i := 0; i < 20; i++ { + for range 20 { assert.False(t, l.Reject(clientIp)) } // Request counter checked and increased. - for i := 0; i < 10; i++ { + for range 10 { assert.True(t, l.Allow(clientIp)) } // Limit exceeded. - for i := 0; i < 10; i++ { + for range 10 { assert.True(t, l.Reject(clientIp)) assert.False(t, l.Allow(clientIp)) } @@ -57,18 +57,18 @@ func TestNewLimit(t *testing.T) { l := NewLimit(0.166, 10) // Request counter not increased. - for i := 0; i < 20; i++ { + for range 20 { assert.False(t, l.Reject(clientIp)) } // Request counter checked and increased. - for i := 0; i < 10; i++ { + for range 10 { assert.False(t, l.Reject(clientIp)) l.Reserve(clientIp) } // Limit exceeded. - for i := 0; i < 10; i++ { + for range 10 { l.Reserve(clientIp) assert.True(t, l.Reject(clientIp)) } @@ -78,7 +78,7 @@ func TestNewLimit(t *testing.T) { l := NewLimit(0.166, 10) // Request counter not increased. - for i := 0; i < 20; i++ { + for range 20 { assert.False(t, l.Reject(clientIp)) } diff --git a/internal/server/limiter/login_test.go b/internal/server/limiter/login_test.go index 7c7104034..949fd3117 100644 --- a/internal/server/limiter/login_test.go +++ b/internal/server/limiter/login_test.go @@ -10,7 +10,7 @@ import ( func TestLogin(t *testing.T) { clientIp := "192.0.2.42" - for i := 0; i < 9; i++ { + for range 9 { t.Logf("tokens now: %f", Login.IP(clientIp).TokensAt(time.Now())) assert.True(t, Login.IP(clientIp).Allow()) } @@ -39,7 +39,7 @@ func TestLogin(t *testing.T) { assert.InEpsilon(t, 10, Login.IP(clientIp).TokensAt(time.Now().Add(DefaultLoginInterval*20)), 0.01) assert.InEpsilon(t, DefaultLoginLimit, Login.IP(clientIp).TokensAt(time.Now().Add(DefaultLoginInterval*DefaultLoginLimit*10)), 0.01) - for i := 0; i < 30; i++ { + for range 30 { assert.False(t, Login.IP(clientIp).Allow()) } diff --git a/internal/server/webdav_auth_test.go b/internal/server/webdav_auth_test.go index eedd8e748..d88ae36c8 100644 --- a/internal/server/webdav_auth_test.go +++ b/internal/server/webdav_auth_test.go @@ -59,7 +59,7 @@ func TestWebDAVAuth(t *testing.T) { } sess := entity.SessionFixtures.Get("alice_token_webdav") - basicAuth := []byte(fmt.Sprintf("alice:%s", sess.AuthToken())) + basicAuth := fmt.Appendf(nil, "alice:%s", sess.AuthToken()) c.Request.Header.Add(header.Auth, fmt.Sprintf("%s %s", header.AuthBasic, base64.StdEncoding.EncodeToString(basicAuth))) webdavAuthCache.Flush() @@ -76,7 +76,7 @@ func TestWebDAVAuth(t *testing.T) { } sess := entity.SessionFixtures.Get("alice_token_webdav") - basicAuth := []byte(fmt.Sprintf("bob:%s", sess.AuthToken())) + basicAuth := fmt.Appendf(nil, "bob:%s", sess.AuthToken()) c.Request.Header.Add(header.Auth, fmt.Sprintf("%s %s", header.AuthBasic, base64.StdEncoding.EncodeToString(basicAuth))) webdavAuthCache.Flush() @@ -93,7 +93,7 @@ func TestWebDAVAuth(t *testing.T) { } sess := entity.SessionFixtures.Get("alice_token_webdav") - basicAuth := []byte(fmt.Sprintf(":%s", sess.AuthToken())) + basicAuth := fmt.Appendf(nil, ":%s", sess.AuthToken()) c.Request.Header.Add(header.Auth, fmt.Sprintf("%s %s", header.AuthBasic, base64.StdEncoding.EncodeToString(basicAuth))) webdavAuthCache.Flush() diff --git a/internal/server/webdav_write_test.go b/internal/server/webdav_write_test.go index 5060d0ac5..4ad40b593 100644 --- a/internal/server/webdav_write_test.go +++ b/internal/server/webdav_write_test.go @@ -33,7 +33,7 @@ func authBearer(req *http.Request) { func authBasic(req *http.Request) { sess := entity.SessionFixtures.Get("alice_token_webdav") - basic := []byte(fmt.Sprintf("alice:%s", sess.AuthToken())) + basic := fmt.Appendf(nil, "alice:%s", sess.AuthToken()) req.Header.Set(header.Auth, fmt.Sprintf("%s %s", header.AuthBasic, base64.StdEncoding.EncodeToString(basic))) } diff --git a/internal/service/cluster/registry/client.go b/internal/service/cluster/registry/client.go index 7af09e004..c0d715db8 100644 --- a/internal/service/cluster/registry/client.go +++ b/internal/service/cluster/registry/client.go @@ -2,6 +2,7 @@ package registry import ( "errors" + "maps" "sort" "time" @@ -142,9 +143,7 @@ func (r *ClientRegistry) Put(n *Node) error { if data.Labels == nil { data.Labels = map[string]string{} } - for k, v := range n.Labels { - data.Labels[k] = v - } + maps.Copy(data.Labels, n.Labels) if n.SiteUrl != "" { data.SiteURL = n.SiteUrl } diff --git a/internal/service/hub/config.go b/internal/service/hub/config.go index f5b632e1b..fd1c2336b 100644 --- a/internal/service/hub/config.go +++ b/internal/service/hub/config.go @@ -267,7 +267,7 @@ func (c *Config) ReSync(token string) (err error) { var r *http.Response // Send request. - for i := 0; i < 3; i++ { + for range 3 { r, err = client.Do(req) if err == nil { diff --git a/internal/service/hub/feedback.go b/internal/service/hub/feedback.go index bc835edc7..14af4c2cc 100644 --- a/internal/service/hub/feedback.go +++ b/internal/service/hub/feedback.go @@ -97,7 +97,7 @@ func (c *Config) SendFeedback(frm form.Feedback) (err error) { var r *http.Response - for i := 0; i < 3; i++ { + for range 3 { r, err = client.Do(req) if err == nil { diff --git a/internal/service/hub/places/location.go b/internal/service/hub/places/location.go index be6a149a6..190fec3cf 100644 --- a/internal/service/hub/places/location.go +++ b/internal/service/hub/places/location.go @@ -20,7 +20,7 @@ type Location struct { LocCategory string `json:"category"` TimeZone string `json:"timezone,omitempty"` Licence string `json:"licence,omitempty"` - Place Place `json:"place,omitempty"` + Place Place `json:"place"` Cached bool `json:"-"` } diff --git a/internal/thumb/frame/angle_test.go b/internal/thumb/frame/angle_test.go index 21c8b952e..f068eca38 100644 --- a/internal/thumb/frame/angle_test.go +++ b/internal/thumb/frame/angle_test.go @@ -8,7 +8,7 @@ import ( func TestRandomAngle(t *testing.T) { t.Run("Valid", func(t *testing.T) { - for i := 0; i < 50; i++ { + for i := range 50 { e := float64(i) a := RandomAngle(e) t.Logf("%f => %f", e, a) diff --git a/internal/thumb/frame/collage.go b/internal/thumb/frame/collage.go index 760b9f5a2..94077d711 100644 --- a/internal/thumb/frame/collage.go +++ b/internal/thumb/frame/collage.go @@ -54,7 +54,7 @@ func polaroidCollage(collage image.Image, images []image.Image) (image.Image, er dl := 1500 / n dr := 1350 / n - for i := 0; i < n; i++ { + for i := range n { img := images[i+1] framed, err := polaroid(img, RandomAngle(30)) diff --git a/internal/thumb/options.go b/internal/thumb/options.go index 489db0241..cd51c527b 100644 --- a/internal/thumb/options.go +++ b/internal/thumb/options.go @@ -1,6 +1,8 @@ package thumb import ( + "slices" + "github.com/photoprism/photoprism/pkg/fs" ) @@ -33,13 +35,7 @@ type Options []ResampleOption // Contains checks if the specified option is set. func (o Options) Contains(option ResampleOption) bool { - for _, v := range o { - if v == option { - return true - } - } - - return false + return slices.Contains(o, option) } // ResampleOptions extracts filter, format, and method from resample options. diff --git a/internal/thumb/vips_icc.go b/internal/thumb/vips_icc.go index 77052ec59..d9bdd34c7 100644 --- a/internal/thumb/vips_icc.go +++ b/internal/thumb/vips_icc.go @@ -2,6 +2,7 @@ package thumb import ( "fmt" + "slices" "github.com/davidbyttow/govips/v2/vips" ) @@ -35,14 +36,7 @@ func vipsSetIccProfileForInteropIndex(img *vips.ImageRef, logName string) (err e // embedding an ICC profile. Browsers and libvips ignore this tag, so we // inject a matching ICC profile to produce correct thumbnails. iiField := "exif-ifd4-InteroperabilityIndex" - hasInterop := false - - for _, field := range img.GetFields() { - if field == iiField { - hasInterop = true - break - } - } + hasInterop := slices.Contains(img.GetFields(), iiField) if !hasInterop { return nil diff --git a/pkg/checksum/digit_test.go b/pkg/checksum/digit_test.go index b73495de7..950b230eb 100644 --- a/pkg/checksum/digit_test.go +++ b/pkg/checksum/digit_test.go @@ -51,7 +51,7 @@ func TestDigit(t *testing.T) { assert.Equal(t, expected, fmt.Sprintf("%d", result)) }) t.Run("Rand", func(t *testing.T) { - for i := 0; i < 100; i++ { + for range 100 { b := make([]byte, 24) if _, err := rand.Read(b); err != nil { diff --git a/pkg/clean/json.go b/pkg/clean/json.go index 2000e86ce..a6c78e244 100644 --- a/pkg/clean/json.go +++ b/pkg/clean/json.go @@ -11,8 +11,8 @@ func JSON(raw string) string { return "" } - if strings.HasPrefix(trimmed, "```") { - trimmed = strings.TrimPrefix(trimmed, "```") + if after, ok := strings.CutPrefix(trimmed, "```"); ok { + trimmed = after trimmed = strings.TrimSpace(trimmed) if !strings.HasPrefix(trimmed, "{") && !strings.HasPrefix(trimmed, "[") { @@ -36,11 +36,7 @@ func JSON(raw string) string { start := -1 switch { case startObj >= 0 && startArr >= 0: - if startObj < startArr { - start = startObj - } else { - start = startArr - } + start = min(startObj, startArr) case startObj >= 0: start = startObj case startArr >= 0: @@ -53,11 +49,7 @@ func JSON(raw string) string { end := -1 switch { case endObj >= 0 && endArr >= 0: - if endObj > endArr { - end = endObj - } else { - end = endArr - } + end = max(endObj, endArr) case endObj >= 0: end = endObj case endArr >= 0: diff --git a/pkg/clean/search.go b/pkg/clean/search.go index a5dbce252..b5529fb2a 100644 --- a/pkg/clean/search.go +++ b/pkg/clean/search.go @@ -29,7 +29,7 @@ func replaceFoldASCII(s, needle, repl string) string { nl := len(needle) // Precompute lower-case needle bytes. nb := make([]byte, nl) - for i := 0; i < nl; i++ { + for i := range nl { nb[i] = toLower(needle[i]) } diff --git a/pkg/dsn/values.go b/pkg/dsn/values.go index 71bb7d82d..9e68de005 100644 --- a/pkg/dsn/values.go +++ b/pkg/dsn/values.go @@ -1,4 +1,4 @@ package dsn // Values is a shorthand alias for map[string]interface{}. -type Values = map[string]interface{} +type Values = map[string]any diff --git a/pkg/fs/duf/mounts.go b/pkg/fs/duf/mounts.go index 66f14cd87..3c9a54d4d 100644 --- a/pkg/fs/duf/mounts.go +++ b/pkg/fs/duf/mounts.go @@ -8,21 +8,21 @@ import ( // Mount contains all metadata for a single filesystem mount. type Mount struct { - Device string `json:"device"` - DeviceType string `json:"device_type"` - Mountpoint string `json:"mount_point"` - Fstype string `json:"fs_type"` - Type string `json:"type"` - Opts string `json:"opts"` - Total uint64 `json:"total"` - Free uint64 `json:"free"` - Used uint64 `json:"used"` - Inodes uint64 `json:"inodes"` - InodesFree uint64 `json:"inodes_free"` - InodesUsed uint64 `json:"inodes_used"` - Blocks uint64 `json:"blocks"` - BlockSize uint64 `json:"block_size"` - Metadata interface{} `json:"-"` + Device string `json:"device"` + DeviceType string `json:"device_type"` + Mountpoint string `json:"mount_point"` + Fstype string `json:"fs_type"` + Type string `json:"type"` + Opts string `json:"opts"` + Total uint64 `json:"total"` + Free uint64 `json:"free"` + Used uint64 `json:"used"` + Inodes uint64 `json:"inodes"` + InodesFree uint64 `json:"inodes_free"` + InodesUsed uint64 `json:"inodes_used"` + Blocks uint64 `json:"blocks"` + BlockSize uint64 `json:"block_size"` + Metadata any `json:"-"` } func readLines(filename string) ([]string, error) { diff --git a/pkg/fs/duf/mounts_linux.go b/pkg/fs/duf/mounts_linux.go index 0983c342e..3e94871ba 100644 --- a/pkg/fs/duf/mounts_linux.go +++ b/pkg/fs/duf/mounts_linux.go @@ -135,7 +135,7 @@ func parseMountInfoLine(line string) (int, [11]string) { } var i int - for _, f := range strings.Fields(line) { + for f := range strings.FieldsSeq(line) { // when parsing the optional fields, loop until we find the separator if i == mountinfoOptionalFields { // (6) optional fields: zero or more fields of the form diff --git a/pkg/fs/duf/util.go b/pkg/fs/duf/util.go index 8a073ba48..ac3e9b9d9 100644 --- a/pkg/fs/duf/util.go +++ b/pkg/fs/duf/util.go @@ -11,7 +11,7 @@ import ( // parseCommaSeparatedValues parses comma separated string into a map. func parseCommaSeparatedValues(values string) FilterValues { m := make(FilterValues) - for _, v := range strings.Split(values, ",") { + for v := range strings.SplitSeq(values, ",") { v = strings.TrimSpace(v) if len(v) == 0 { continue diff --git a/pkg/fs/fastwalk/fastwalk_dirent_namlen_linux.go b/pkg/fs/fastwalk/fastwalk_dirent_namlen_linux.go index be8cc9f19..582f856bd 100644 --- a/pkg/fs/fastwalk/fastwalk_dirent_namlen_linux.go +++ b/pkg/fs/fastwalk/fastwalk_dirent_namlen_linux.go @@ -16,10 +16,7 @@ func direntNamlen(dirent *syscall.Dirent) uint64 { const fixedHdr = uint16(unsafe.Offsetof(syscall.Dirent{}.Name)) nameBuf := (*[unsafe.Sizeof(dirent.Name)]byte)(unsafe.Pointer(&dirent.Name[0])) //nolint:gosec // bounded by Dirent name buffer size const nameBufLen = uint16(len(nameBuf)) - limit := dirent.Reclen - fixedHdr - if limit > nameBufLen { - limit = nameBufLen - } + limit := min(dirent.Reclen-fixedHdr, nameBufLen) nameLen := bytes.IndexByte(nameBuf[:limit], 0) if nameLen < 0 { panic("failed to find terminating 0 byte in dirent") diff --git a/pkg/fs/fastwalk/fastwalk_test.go b/pkg/fs/fastwalk/fastwalk_test.go index 4255a0511..e9162dc0f 100644 --- a/pkg/fs/fastwalk/fastwalk_test.go +++ b/pkg/fs/fastwalk/fastwalk_test.go @@ -52,8 +52,8 @@ func testFastWalk(t *testing.T, files map[string]string, callback func(path stri t.Fatal(err) } - if strings.HasPrefix(contents, "LINK:") { - err = os.Symlink(strings.TrimPrefix(contents, "LINK:"), file) + if after, ok := strings.CutPrefix(contents, "LINK:"); ok { + err = os.Symlink(after, file) } else { err = os.WriteFile(file, []byte(contents), 0o600) } diff --git a/pkg/fs/ignore_test.go b/pkg/fs/ignore_test.go index 46f291baa..57349d148 100644 --- a/pkg/fs/ignore_test.go +++ b/pkg/fs/ignore_test.go @@ -254,12 +254,12 @@ func TestIgnoreList_ConcurrentAccess(t *testing.T) { var wg sync.WaitGroup errCh := make(chan error, 64) - for i := 0; i < 32; i++ { + for range 32 { wg.Add(1) go func() { defer wg.Done() - for j := 0; j < 64; j++ { + for range 64 { if err := ignore.Path(aDir); err != nil { errCh <- err return diff --git a/pkg/fs/zip_test.go b/pkg/fs/zip_test.go index c202df4da..8567beb4a 100644 --- a/pkg/fs/zip_test.go +++ b/pkg/fs/zip_test.go @@ -189,7 +189,7 @@ func TestUnzip_EntryLimit(t *testing.T) { zipPath := filepath.Join(dir, "limit.zip") entries := map[string][]byte{} - for i := 0; i < 5; i++ { + for i := range 5 { entries[fmt.Sprintf("f%d.txt", i)] = []byte("x") } writeZip(t, zipPath, entries) diff --git a/pkg/http/dns/domain_test.go b/pkg/http/dns/domain_test.go index aa00a1881..4bc661327 100644 --- a/pkg/http/dns/domain_test.go +++ b/pkg/http/dns/domain_test.go @@ -37,7 +37,7 @@ func Test_IsDNSDomain(t *testing.T) { // helper: fast string repeat without importing strings just for tests func stringsRepeat(s string, n int) string { b := make([]byte, 0, len(s)*n) - for i := 0; i < n; i++ { + for range n { b = append(b, s...) } return string(b) diff --git a/pkg/http/header/auth_test.go b/pkg/http/header/auth_test.go index c565fafb4..5798c15a0 100644 --- a/pkg/http/header/auth_test.go +++ b/pkg/http/header/auth_test.go @@ -38,7 +38,7 @@ func RandomAppPassword() string { m := big.NewInt(int64(len(CharsetBase62))) b := make([]byte, 0, 27) - for i := 0; i < 27; i++ { + for i := range 27 { if (i+1)%7 == 0 { b = append(b, '-') } else if i == 27-1 { diff --git a/pkg/i18n/i18n.go b/pkg/i18n/i18n.go index 2539753de..c06ed44c1 100644 --- a/pkg/i18n/i18n.go +++ b/pkg/i18n/i18n.go @@ -40,14 +40,14 @@ type Message int // MessageMap maps message IDs to their localized strings. type MessageMap map[Message]string -var noVars []interface{} +var noVars []any func gettext(s string) string { return gotext.Get(s, noVars...) } // msgParams replaces message params with the actual values. -func msgParams(msg string, params ...interface{}) string { +func msgParams(msg string, params ...any) string { if strings.Contains(msg, "%") { msg = fmt.Sprintf(msg, params...) } @@ -56,16 +56,16 @@ func msgParams(msg string, params ...interface{}) string { } // Msg returns a translated message string. -func Msg(id Message, params ...interface{}) string { +func Msg(id Message, params ...any) string { return msgParams(gotext.Get(Messages[id], noVars...), params...) } // Error returns a translated error message. -func Error(id Message, params ...interface{}) error { +func Error(id Message, params ...any) error { return errors.New(Msg(id, params...)) } // Lower returns the untranslated message as a lowercase string for use in logs. -func Lower(id Message, params ...interface{}) string { +func Lower(id Message, params ...any) string { return strings.ToLower(msgParams(Messages[id], params...)) } diff --git a/pkg/i18n/response.go b/pkg/i18n/response.go index 4868b4a03..39f70208f 100644 --- a/pkg/i18n/response.go +++ b/pkg/i18n/response.go @@ -33,7 +33,7 @@ func (r Response) Success() bool { } // NewResponse builds a Response with the given code, message ID, and optional parameters. -func NewResponse(code int, id Message, params ...interface{}) Response { +func NewResponse(code int, id Message, params ...any) Response { if code < 400 { return Response{Code: code, Msg: Msg(id, params...)} } else { diff --git a/pkg/list/bench_test.go b/pkg/list/bench_test.go index 71818db0f..6042e3d58 100644 --- a/pkg/list/bench_test.go +++ b/pkg/list/bench_test.go @@ -7,7 +7,7 @@ import ( func makeStrings(prefix string, n int) []string { out := make([]string, n) - for i := 0; i < n; i++ { + for i := range n { out[i] = fmt.Sprintf("%s_%06d", prefix, i) } return out @@ -29,7 +29,7 @@ func BenchmarkContainsAny_LargeOverlap(b *testing.B) { a := makeStrings("a", 5000) bList := makeStrings("b", 5000) // Introduce overlap: copy 20% of a into bList - for i := 0; i < 1000; i++ { + for i := range 1000 { bList[i] = a[i*4] } b.ReportAllocs() diff --git a/pkg/list/contains.go b/pkg/list/contains.go index fe11d6594..34ab9e887 100644 --- a/pkg/list/contains.go +++ b/pkg/list/contains.go @@ -1,5 +1,7 @@ package list +import "slices" + // Any matches everything. const Any = "*" @@ -30,10 +32,8 @@ func ContainsAny(l, s []string) bool { return false } - for _, v := range s { - if v == Any { - return true - } + if slices.Contains(s, Any) { + return true } // Build a set from the smaller slice for O(n+m) intersection. diff --git a/pkg/list/ordered/map_test.go b/pkg/list/ordered/map_test.go index c58d1d643..a53ecf6d5 100644 --- a/pkg/list/ordered/map_test.go +++ b/pkg/list/ordered/map_test.go @@ -141,7 +141,7 @@ func TestReplaceKey(t *testing.T) { count := 100 // Build a larger map to help validate that the order is not coincidental. m := ordered.NewMap[int, int]() - for i := 0; i < count; i++ { + for i := range count { m.Set(i, i) } // Rename the middle 50-60 elements to 100+ current @@ -344,7 +344,7 @@ func TestIterations(t *testing.T) { m.Set(v.Key, v.Value) } element := m.Front() - for i := 0; i < len(expected); i++ { + for i := range expected { assert.NotNil(t, element) assert.Equal(t, expected[i].Key, element.Key) assert.Equal(t, expected[i].Value, element.Value) @@ -801,7 +801,7 @@ func BenchmarkOrderedMapString_Has(b *testing.B) { benchmarkOrderedMapString_Has(1)(b) } -func nothing(v interface{}) { +func nothing(v any) { _ = v } @@ -809,7 +809,7 @@ func benchmarkBigMap_Set() func(b *testing.B) { return func(b *testing.B) { for j := 0; j < b.N; j++ { m := make(map[int]bool) - for i := 0; i < 10000000; i++ { + for i := range 10000000 { m[i] = true } } @@ -824,7 +824,7 @@ func benchmarkBigOrderedMap_Set() func(b *testing.B) { return func(b *testing.B) { for j := 0; j < b.N; j++ { m := ordered.NewMap[int, bool]() - for i := 0; i < 10000000; i++ { + for i := range 10000000 { m.Set(i, true) } } @@ -839,7 +839,7 @@ func benchmarkBigMapWithCapacity_Set() func(b *testing.B) { return func(b *testing.B) { for j := 0; j < b.N; j++ { m := ordered.NewMapWithCapacity[int, bool](10000000) - for i := 0; i < 10000000; i++ { + for i := range 10000000 { m.Set(i, true) } } @@ -852,13 +852,13 @@ func BenchmarkBigMapWithCapacity_Set(b *testing.B) { func benchmarkBigMap_Get() func(b *testing.B) { m := make(map[int]bool) - for i := 0; i < 10000000; i++ { + for i := range 10000000 { m[i] = true } return func(b *testing.B) { for j := 0; j < b.N; j++ { - for i := 0; i < 10000000; i++ { + for i := range 10000000 { _ = m[i] } } @@ -871,13 +871,13 @@ func BenchmarkBigMap_Get(b *testing.B) { func benchmarkBigOrderedMap_Get() func(b *testing.B) { m := ordered.NewMap[int, bool]() - for i := 0; i < 10000000; i++ { + for i := range 10000000 { m.Set(i, true) } return func(b *testing.B) { for j := 0; j < b.N; j++ { - for i := 0; i < 10000000; i++ { + for i := range 10000000 { m.Get(i) } } @@ -890,13 +890,13 @@ func BenchmarkBigOrderedMap_Get(b *testing.B) { func benchmarkBigOrderedMap_GetElement() func(b *testing.B) { m := ordered.NewMap[int, bool]() - for i := 0; i < 10000000; i++ { + for i := range 10000000 { m.Set(i, true) } return func(b *testing.B) { for j := 0; j < b.N; j++ { - for i := 0; i < 10000000; i++ { + for i := range 10000000 { m.GetElement(i) } } @@ -909,7 +909,7 @@ func BenchmarkBigOrderedMap_GetElement(b *testing.B) { func benchmarkBigMap_Iterate() func(b *testing.B) { m := make(map[int]bool) - for i := 0; i < 10000000; i++ { + for i := range 10000000 { m[i] = true } return func(b *testing.B) { @@ -927,7 +927,7 @@ func BenchmarkBigMap_Iterate(b *testing.B) { func benchmarkBigOrderedMap_Iterate() func(b *testing.B) { m := ordered.NewMap[int, bool]() - for i := 0; i < 10000000; i++ { + for i := range 10000000 { m.Set(i, true) } @@ -950,7 +950,7 @@ func benchmarkBigMapString_Set() func(b *testing.B) { for j := 0; j < b.N; j++ { m := make(map[string]bool) a := "1234567" - for i := 0; i < 10000000; i++ { + for i := range 10000000 { m[a+strconv.Itoa(i)] = true } } @@ -959,12 +959,12 @@ func benchmarkBigMapString_Set() func(b *testing.B) { func benchmarkBigMap_Has() func(b *testing.B) { m := make(map[int]bool) - for i := 0; i < 10000000; i++ { + for i := range 10000000 { m[i] = true } return func(b *testing.B) { for j := 0; j < b.N; j++ { - for i := 0; i < 10000000; i++ { + for i := range 10000000 { _ = m[i] } } @@ -977,12 +977,12 @@ func BenchmarkBigMap_Has(b *testing.B) { func benchmarkBigOrderedMap_Has() func(b *testing.B) { m := ordered.NewMap[int, bool]() - for i := 0; i < 10000000; i++ { + for i := range 10000000 { m.Set(i, true) } return func(b *testing.B) { for j := 0; j < b.N; j++ { - for i := 0; i < 10000000; i++ { + for i := range 10000000 { m.Has(i) } } @@ -1002,7 +1002,7 @@ func benchmarkBigOrderedMapString_Set() func(b *testing.B) { for j := 0; j < b.N; j++ { m := ordered.NewMap[string, bool]() a := "1234567" - for i := 0; i < 10000000; i++ { + for i := range 10000000 { m.Set(a+strconv.Itoa(i), true) } } @@ -1016,13 +1016,13 @@ func BenchmarkBigOrderedMapString_Set(b *testing.B) { func benchmarkBigMapString_Get() func(b *testing.B) { m := make(map[string]bool) a := "1234567" - for i := 0; i < 10000000; i++ { + for i := range 10000000 { m[a+strconv.Itoa(i)] = true } return func(b *testing.B) { for j := 0; j < b.N; j++ { - for i := 0; i < 10000000; i++ { + for i := range 10000000 { _ = m[a+strconv.Itoa(i)] } } @@ -1036,13 +1036,13 @@ func BenchmarkBigMapString_Get(b *testing.B) { func benchmarkBigOrderedMapString_Get() func(b *testing.B) { m := ordered.NewMap[string, bool]() a := "1234567" - for i := 0; i < 10000000; i++ { + for i := range 10000000 { m.Set(a+strconv.Itoa(i), true) } return func(b *testing.B) { for j := 0; j < b.N; j++ { - for i := 0; i < 10000000; i++ { + for i := range 10000000 { m.Get(a + strconv.Itoa(i)) } } @@ -1056,13 +1056,13 @@ func BenchmarkBigOrderedMapString_Get(b *testing.B) { func benchmarkBigOrderedMapString_GetElement() func(b *testing.B) { m := ordered.NewMap[string, bool]() a := "1234567" - for i := 0; i < 10000000; i++ { + for i := range 10000000 { m.Set(a+strconv.Itoa(i), true) } return func(b *testing.B) { for j := 0; j < b.N; j++ { - for i := 0; i < 10000000; i++ { + for i := range 10000000 { m.GetElement(a + strconv.Itoa(i)) } } @@ -1076,7 +1076,7 @@ func BenchmarkBigOrderedMapString_GetElement(b *testing.B) { func benchmarkBigMapString_Iterate() func(b *testing.B) { m := make(map[string]bool) a := "12345678" - for i := 0; i < 10000000; i++ { + for i := range 10000000 { m[a+strconv.Itoa(i)] = true } return func(b *testing.B) { @@ -1095,7 +1095,7 @@ func BenchmarkBigMapString_Iterate(b *testing.B) { func benchmarkBigOrderedMapString_Iterate() func(b *testing.B) { m := ordered.NewMap[string, bool]() a := "12345678" - for i := 0; i < 10000000; i++ { + for i := range 10000000 { m.Set(a+strconv.Itoa(i), true) } @@ -1116,12 +1116,12 @@ func BenchmarkBigOrderedMapString_Iterate(b *testing.B) { func benchmarkBigMapString_Has() func(b *testing.B) { m := make(map[string]bool) a := "12345678" - for i := 0; i < 10000000; i++ { + for i := range 10000000 { m[a+strconv.Itoa(i)] = true } return func(b *testing.B) { for j := 0; j < b.N; j++ { - for i := 0; i < 10000000; i++ { + for i := range 10000000 { _ = m[a+strconv.Itoa(i)] } } @@ -1135,12 +1135,12 @@ func BenchmarkBigMapString_Has(b *testing.B) { func benchmarkBigOrderedMapString_Has() func(b *testing.B) { m := ordered.NewMap[string, bool]() a := "12345678" - for i := 0; i < 10000000; i++ { + for i := range 10000000 { m.Set(a+strconv.Itoa(i), true) } return func(b *testing.B) { for j := 0; j < b.N; j++ { - for i := 0; i < 10000000; i++ { + for i := range 10000000 { m.Has(a + strconv.Itoa(i)) } } diff --git a/pkg/list/ordered/sync_test.go b/pkg/list/ordered/sync_test.go index e20000947..5efb49f2c 100644 --- a/pkg/list/ordered/sync_test.go +++ b/pkg/list/ordered/sync_test.go @@ -88,7 +88,7 @@ func TestRaceCondition(t *testing.T) { }() } - for i := 0; i < 10000; i++ { + for range 10000 { asyncSet() asyncGet() asyncDelete() diff --git a/pkg/log/dummy/logger.go b/pkg/log/dummy/logger.go index bde7a6d5b..5ceb21854 100644 --- a/pkg/log/dummy/logger.go +++ b/pkg/log/dummy/logger.go @@ -41,7 +41,7 @@ func NewLogger() *Logger { } // WithField allocates a new entry and adds a field to it. -func (logger *Logger) WithField(key string, value interface{}) *logrus.Entry { +func (logger *Logger) WithField(key string, value any) *logrus.Entry { return &logrus.Entry{Data: logrus.Fields{key: value}} } @@ -70,68 +70,68 @@ func (logger *Logger) WithTime(t time.Time) *logrus.Entry { return &logrus.Entry{Time: t} } -func (logger *Logger) Logf(level logrus.Level, format string, args ...interface{}) { +func (logger *Logger) Logf(level logrus.Level, format string, args ...any) { } -func (logger *Logger) Tracef(format string, args ...interface{}) { +func (logger *Logger) Tracef(format string, args ...any) { } -func (logger *Logger) Debugf(format string, args ...interface{}) { +func (logger *Logger) Debugf(format string, args ...any) { } -func (logger *Logger) Infof(format string, args ...interface{}) { +func (logger *Logger) Infof(format string, args ...any) { } -func (logger *Logger) Printf(format string, args ...interface{}) { +func (logger *Logger) Printf(format string, args ...any) { } -func (logger *Logger) Warnf(format string, args ...interface{}) { +func (logger *Logger) Warnf(format string, args ...any) { } -func (logger *Logger) Warningf(format string, args ...interface{}) { +func (logger *Logger) Warningf(format string, args ...any) { } -func (logger *Logger) Errorf(format string, args ...interface{}) { +func (logger *Logger) Errorf(format string, args ...any) { } -func (logger *Logger) Fatalf(format string, args ...interface{}) { +func (logger *Logger) Fatalf(format string, args ...any) { } -func (logger *Logger) Panicf(format string, args ...interface{}) { +func (logger *Logger) Panicf(format string, args ...any) { } // Log will log a message at the level given as parameter. -func (logger *Logger) Log(level logrus.Level, args ...interface{}) { +func (logger *Logger) Log(level logrus.Level, args ...any) { } func (logger *Logger) LogFn(level logrus.Level, fn logrus.LogFunction) { } -func (logger *Logger) Trace(args ...interface{}) { +func (logger *Logger) Trace(args ...any) { } -func (logger *Logger) Debug(args ...interface{}) { +func (logger *Logger) Debug(args ...any) { } -func (logger *Logger) Info(args ...interface{}) { +func (logger *Logger) Info(args ...any) { } -func (logger *Logger) Print(args ...interface{}) { +func (logger *Logger) Print(args ...any) { } -func (logger *Logger) Warn(args ...interface{}) { +func (logger *Logger) Warn(args ...any) { } -func (logger *Logger) Warning(args ...interface{}) { +func (logger *Logger) Warning(args ...any) { } -func (logger *Logger) Error(args ...interface{}) { +func (logger *Logger) Error(args ...any) { } -func (logger *Logger) Fatal(args ...interface{}) { +func (logger *Logger) Fatal(args ...any) { } -func (logger *Logger) Panic(args ...interface{}) { +func (logger *Logger) Panic(args ...any) { } func (logger *Logger) TraceFn(fn logrus.LogFunction) { @@ -161,39 +161,39 @@ func (logger *Logger) FatalFn(fn logrus.LogFunction) { func (logger *Logger) PanicFn(fn logrus.LogFunction) { } -func (logger *Logger) Logln(level logrus.Level, args ...interface{}) { +func (logger *Logger) Logln(level logrus.Level, args ...any) { } -func (logger *Logger) Traceln(args ...interface{}) { +func (logger *Logger) Traceln(args ...any) { } -func (logger *Logger) Debugln(args ...interface{}) { +func (logger *Logger) Debugln(args ...any) { } -func (logger *Logger) Infoln(args ...interface{}) { +func (logger *Logger) Infoln(args ...any) { logger.Logln(logrus.InfoLevel, args...) } -func (logger *Logger) Println(args ...interface{}) { +func (logger *Logger) Println(args ...any) { } -func (logger *Logger) Warnln(args ...interface{}) { +func (logger *Logger) Warnln(args ...any) { logger.Logln(logrus.WarnLevel, args...) } -func (logger *Logger) Warningln(args ...interface{}) { +func (logger *Logger) Warningln(args ...any) { logger.Warnln(args...) } -func (logger *Logger) Errorln(args ...interface{}) { +func (logger *Logger) Errorln(args ...any) { logger.Logln(logrus.ErrorLevel, args...) } -func (logger *Logger) Fatalln(args ...interface{}) { +func (logger *Logger) Fatalln(args ...any) { logger.Logln(logrus.FatalLevel, args...) } -func (logger *Logger) Panicln(args ...interface{}) { +func (logger *Logger) Panicln(args ...any) { logger.Logln(logrus.PanicLevel, args...) } diff --git a/pkg/log/status/error_test.go b/pkg/log/status/error_test.go index eb6e92c0b..9f0e378e6 100644 --- a/pkg/log/status/error_test.go +++ b/pkg/log/status/error_test.go @@ -29,7 +29,6 @@ func TestError(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/pkg/rnd/auth.go b/pkg/rnd/auth.go index bce502aa3..9f11b29ec 100644 --- a/pkg/rnd/auth.go +++ b/pkg/rnd/auth.go @@ -65,7 +65,7 @@ func AppPassword() string { m := big.NewInt(int64(len(CharsetBase62))) b := make([]byte, 0, AppPasswordLength) - for i := 0; i < AppPasswordLength; i++ { + for i := range AppPasswordLength { if (i+1)%7 == 0 { b = append(b, Separator) } else if i == AppPasswordLength-1 { @@ -115,7 +115,7 @@ func JoinToken() string { m := big.NewInt(int64(len(CharsetBase62))) token := make([]byte, 0, JoinTokenLength) - for i := 0; i < JoinTokenLength-1; i++ { + for i := range JoinTokenLength - 1 { if isJoinTokenSeparatorIndex(i) { token = append(token, Separator) continue diff --git a/pkg/rnd/auth_test.go b/pkg/rnd/auth_test.go index edf29851a..ae25fe227 100644 --- a/pkg/rnd/auth_test.go +++ b/pkg/rnd/auth_test.go @@ -14,7 +14,7 @@ func TestAuthToken(t *testing.T) { assert.True(t, IsAuthToken(result)) assert.True(t, IsHex(result)) - for n := 0; n < 10; n++ { + for n := range 10 { s := AuthToken() t.Logf("AuthToken %d: %s", n, s) assert.NotEmpty(t, s) @@ -54,7 +54,7 @@ func TestAuthTokenID(t *testing.T) { assert.True(t, unicode.IsDigit(r) || (r >= 'a' && r <= 'z')) } - for n := 0; n < 10; n++ { + for n := range 10 { s := AuthTokenID("jwt") t.Logf("AuthTokenID %d: %s", n, s) assert.NotEmpty(t, s) @@ -65,7 +65,7 @@ func TestAuthTokenID(t *testing.T) { } func TestAppPassword(t *testing.T) { - for n := 0; n < 10; n++ { + for n := range 10 { s := AppPassword() t.Logf("AppPassword %d: %s", n, s) assert.Equal(t, AppPasswordLength, len(s)) @@ -126,7 +126,7 @@ func BenchmarkAppPasswordIgnoreChecksum(b *testing.B) { } func TestJoinToken(t *testing.T) { - for n := 0; n < 10; n++ { + for n := range 10 { s := JoinToken() t.Logf("JoinToken %d: %s", n, s) assert.Equal(t, JoinTokenLength, len(s)) @@ -197,7 +197,7 @@ func TestSessionID(t *testing.T) { assert.Equal(t, SessionIdLength, len(result)) assert.Equal(t, "f22383a703805a031a9835c8c6b6dafb793a21e8f33d0b4887b4ec9bd7ac8cd5", result) - for n := 0; n < 10; n++ { + for n := range 10 { s := SessionID(AuthToken()) t.Logf("SessionID %d: %s", n, s) assert.NotEmpty(t, s) diff --git a/pkg/rnd/client_test.go b/pkg/rnd/client_test.go index 063faec97..b576ee284 100644 --- a/pkg/rnd/client_test.go +++ b/pkg/rnd/client_test.go @@ -14,7 +14,7 @@ func TestClientSecret(t *testing.T) { assert.False(t, IsAuthToken(result)) assert.False(t, IsHex(result)) - for n := 0; n < 10; n++ { + for n := range 10 { s := ClientSecret() t.Logf("ClientSecret %d: %s", n, s) assert.True(t, IsClientSecret(s)) diff --git a/pkg/rnd/name_test.go b/pkg/rnd/name_test.go index ed8c26304..e77984f23 100644 --- a/pkg/rnd/name_test.go +++ b/pkg/rnd/name_test.go @@ -12,7 +12,7 @@ func TestName(t *testing.T) { assert.NotEmpty(t, name) assert.Equal(t, 1, strings.Count(name, " ")) - for n := 0; n < 10; n++ { + for n := range 10 { s := Name() t.Logf("Name %d: %s", n, s) assert.NotEmpty(t, s) @@ -31,7 +31,7 @@ func TestNameN(t *testing.T) { assert.NotEmpty(t, name) assert.Equal(t, 1, strings.Count(name, " ")) - for n := 0; n < 10; n++ { + for n := range 10 { s := NameN(n + 1) t.Logf("NameN %d: %s", n, s) assert.NotEmpty(t, s) diff --git a/pkg/rnd/token_test.go b/pkg/rnd/token_test.go index 3beb1fb28..005359cf8 100644 --- a/pkg/rnd/token_test.go +++ b/pkg/rnd/token_test.go @@ -15,7 +15,7 @@ func TestBase10(t *testing.T) { assert.False(t, InvalidRefID(s)) assert.Equal(t, 10, len(s)) - for n := 0; n < 10; n++ { + for n := range 10 { s = Base10(10) t.Logf("Base10 %d: %s", n, s) assert.NotEmpty(t, s) @@ -40,7 +40,7 @@ func TestBase36(t *testing.T) { assert.False(t, InvalidRefID(s)) assert.Equal(t, 10, len(s)) - for n := 0; n < 10; n++ { + for n := range 10 { s = Base36(10) t.Logf("Base36 %d: %s", n, s) assert.NotEmpty(t, s) @@ -58,7 +58,7 @@ func TestBase36(t *testing.T) { func TestBase62(t *testing.T) { t.Run("Ten", func(t *testing.T) { - for n := 0; n < 10; n++ { + for n := range 10 { s := Base62(10) t.Logf("Base62 %d: %s", n, s) assert.NotEmpty(t, s) @@ -73,7 +73,7 @@ func TestBase62(t *testing.T) { assert.Equal(t, 23, len(s)) }) t.Run("Num32", func(t *testing.T) { - for n := 0; n < 10; n++ { + for n := range 10 { s := Base62(32) t.Logf("Base62 (32 chars) %d: %s", n, s) assert.NotEmpty(t, s) @@ -118,7 +118,7 @@ func TestRandomToken(t *testing.T) { assert.NotEmpty(t, s) }) t.Run("Log", func(t *testing.T) { - for n := 0; n < 10; n++ { + for n := range 10 { s := Base36(8) t.Logf("%d: %s", n, s) assert.NotEmpty(t, s) diff --git a/pkg/rnd/uid_test.go b/pkg/rnd/uid_test.go index fdaa5478b..bd01693bc 100644 --- a/pkg/rnd/uid_test.go +++ b/pkg/rnd/uid_test.go @@ -23,7 +23,7 @@ func TestIsUnique(t *testing.T) { func TestIsUID(t *testing.T) { prefix := byte('x') - for n := 0; n < 10; n++ { + for n := range 10 { s := GenerateUID(prefix) t.Logf("UID %d: %s", n, s) assert.True(t, IsUID(s, prefix)) @@ -41,7 +41,7 @@ func TestIsUID(t *testing.T) { func TestInvalidUID(t *testing.T) { prefix := byte('x') - for n := 0; n < 10; n++ { + for range 10 { id := GenerateUID(prefix) assert.False(t, InvalidUID(id, prefix)) } @@ -71,7 +71,7 @@ func TestIsAlnum(t *testing.T) { } func TestGenerateUID(t *testing.T) { - for n := 0; n < 5; n++ { + for n := range 5 { uid := GenerateUID('c') t.Logf("UID %d: %s", n, uid) assert.Equal(t, len(uid), 16) diff --git a/pkg/rnd/uuid_test.go b/pkg/rnd/uuid_test.go index 333fd4121..fd6f6fb0f 100644 --- a/pkg/rnd/uuid_test.go +++ b/pkg/rnd/uuid_test.go @@ -7,7 +7,7 @@ import ( ) func TestUUID(t *testing.T) { - for n := 0; n < 5; n++ { + for n := range 5 { s := UUID() t.Logf("UUID %d: %s", n, s) assert.Equal(t, 36, len(s)) @@ -21,7 +21,7 @@ func BenchmarkUUID(b *testing.B) { } func TestState(t *testing.T) { - for n := 0; n < 5; n++ { + for n := range 5 { s := State() t.Logf("UUID %d: %s", n, s) assert.Equal(t, 36, len(s)) diff --git a/pkg/txt/datetime.go b/pkg/txt/datetime.go index 36e25217a..171d0198f 100644 --- a/pkg/txt/datetime.go +++ b/pkg/txt/datetime.go @@ -52,14 +52,14 @@ const OneYear = time.Hour * 24 * 365 func init() { en := ExifDateTimeRegexp.SubexpNames() - for i := 0; i < len(en); i++ { + for i := range en { if name := en[i]; name != "" { ExifDateTimeMatch[name] = i } } hn := HumanDateTimeRegexp.SubexpNames() - for i := 0; i < len(hn); i++ { + for i := range hn { if name := hn[i]; name != "" { HumanDateTimeMatch[name] = i } diff --git a/pkg/txt/list.go b/pkg/txt/list.go index 9429a20da..e79ec4f4a 100644 --- a/pkg/txt/list.go +++ b/pkg/txt/list.go @@ -21,7 +21,7 @@ func JoinAnd(values []string) string { // length >= 3 result := "" - for i := 0; i < length; i++ { + for i := range length { switch i { case 0: result = values[i] diff --git a/pkg/txt/words_bench_test.go b/pkg/txt/words_bench_test.go index 275343505..09ef9697d 100644 --- a/pkg/txt/words_bench_test.go +++ b/pkg/txt/words_bench_test.go @@ -24,7 +24,7 @@ func makeLargeText(distinct, repeats int) string { var sb strings.Builder // Rough preallocation: average word ~6 chars + space sb.Grow(distinct * repeats * 8) - for r := 0; r < repeats; r++ { + for r := range repeats { for i, w := range base { if i%17 == 0 { sb.WriteString(" ") diff --git a/pkg/vector/alg/common.go b/pkg/vector/alg/common.go index 575463161..5f7562696 100644 --- a/pkg/vector/alg/common.go +++ b/pkg/vector/alg/common.go @@ -41,7 +41,7 @@ func (pq priorityQueue) Swap(i, j int) { pq[j].i = j } -func (pq *priorityQueue) Push(x interface{}) { +func (pq *priorityQueue) Push(x any) { n := len(*pq) item := x.(*pItem) item.i = n @@ -49,7 +49,7 @@ func (pq *priorityQueue) Push(x interface{}) { heap.Fix(pq, item.i) } -func (pq *priorityQueue) Pop() interface{} { +func (pq *priorityQueue) Pop() any { old := *pq n := len(old) item := old[n-1] @@ -76,7 +76,7 @@ func bounds(data [][]float64) []*[2]float64 { r = make([]*[2]float64, l) ) - for i := 0; i < l; i++ { + for i := range l { r[i] = &[2]float64{ data[0][i], data[0][i], @@ -85,11 +85,11 @@ func bounds(data [][]float64) []*[2]float64 { wg.Add(l) - for i := 0; i < l; i++ { + for i := range l { go func(n int) { defer wg.Done() - for j := 0; j < len(data); j++ { + for j := range data { if data[j][n] < r[n][0] { r[n][0] = data[j][n] } else if data[j][n] > r[n][1] { diff --git a/pkg/vector/alg/common_test.go b/pkg/vector/alg/common_test.go index 0b8073ada..316b8fa81 100644 --- a/pkg/vector/alg/common_test.go +++ b/pkg/vector/alg/common_test.go @@ -126,7 +126,7 @@ func TestUniform(t *testing.T) { } ) - for i := 0; i < l; i++ { + for range l { u := uniform(d) if u < 0 || u > 10 { t.Error("Unformly distributed variable out of bounds") diff --git a/pkg/vector/alg/csv_importer_test.go b/pkg/vector/alg/csv_importer_test.go index 5e3a70a35..38d610c44 100644 --- a/pkg/vector/alg/csv_importer_test.go +++ b/pkg/vector/alg/csv_importer_test.go @@ -50,7 +50,7 @@ func fsliceEqual(a, b [][]float64) bool { return false } - for i := 0; i < len(a); i++ { + for i := range a { if len(a[i]) != len(b[i]) { return false } diff --git a/pkg/vector/alg/json_importer.go b/pkg/vector/alg/json_importer.go index 189f4cac7..009eca177 100644 --- a/pkg/vector/alg/json_importer.go +++ b/pkg/vector/alg/json_importer.go @@ -43,7 +43,7 @@ func (i *jsonImporter) Import(file string, start, end int) ([][]float64, error) } d[i] = make([]float64, 0, s) - for j := 0; j < s; j++ { + for j := range s { d[i][j] = g[j] } } diff --git a/pkg/vector/alg/kmeans.go b/pkg/vector/alg/kmeans.go index e94c435a4..01c838025 100644 --- a/pkg/vector/alg/kmeans.go +++ b/pkg/vector/alg/kmeans.go @@ -172,7 +172,7 @@ func (c *kmeansClusterer) Online(observations chan []float64, done chan struct{} Observation: o, } - for i := 0; i < f; i++ { + for i := range f { c.m[k][i] = c.alpha*o[i] + h*c.m[k][i] } @@ -302,7 +302,7 @@ func (c *kmeansClusterer) run() { for i := 0; i < c.number; i++ { floats.Scale(1/float64(c.b[i]), c.n[i]) - for j := 0; j < l; j++ { + for j := range l { c.m[i][j] = c.n[i][j] c.n[i][j] = 0 } diff --git a/pkg/vector/alg/kmeans_estimator.go b/pkg/vector/alg/kmeans_estimator.go index 14c3723cd..3fe0acc36 100644 --- a/pkg/vector/alg/kmeans_estimator.go +++ b/pkg/vector/alg/kmeans_estimator.go @@ -201,7 +201,7 @@ func (c *kmeansEstimator) run() { for i := 0; i < c.number; i++ { floats.Scale(1/float64(c.b[i]), c.n[i]) - for j := 0; j < l; j++ { + for j := range l { c.m[i][j] = c.n[i][j] c.n[i][j] = 0 } @@ -222,7 +222,7 @@ func (c *kmeansEstimator) wk(data [][]float64, centroids [][]float64, mapping [] wk = make([]float64, len(centroids)) ) - for i := 0; i < len(mapping); i++ { + for i := range mapping { wk[mapping[i]-1] += EuclideanDistSquared(centroids[mapping[i]-1], data[i]) / l } @@ -235,10 +235,10 @@ func (c *kmeansEstimator) buildRandomizedSet(size int, bounds []*[2]float64) [][ r = make([][]float64, size) ) - for i := 0; i < size; i++ { + for i := range size { r[i] = make([]float64, l) - for j := 0; j < l; j++ { + for j := range l { r[i][j] = uniform(bounds[j]) } } diff --git a/pkg/vector/alg/optics.go b/pkg/vector/alg/optics.go index 9016cecc5..345991eac 100644 --- a/pkg/vector/alg/optics.go +++ b/pkg/vector/alg/optics.go @@ -231,7 +231,7 @@ func (c *opticsClusterer) coreDist(p int, l int, r []int) float64 { } func (c *opticsClusterer) update(p int, d float64, l int, r []int, q *priorityQueue) { - for i := 0; i < l; i++ { + for i := range l { if !c.v[r[i]] { m := math.Max(d, c.distance(c.d[p], c.d[r[i]])) diff --git a/pkg/vector/values.go b/pkg/vector/values.go index cd2643e22..3f7b864d8 100644 --- a/pkg/vector/values.go +++ b/pkg/vector/values.go @@ -186,7 +186,7 @@ func CosineDist(a, b Vector) float64 { var sum, s1, s2 float64 - for i := 0; i < len(a); i++ { + for i := range a { sum += a[i] * b[i] s1 += a[i] * a[i] s2 += b[i] * b[i] diff --git a/pkg/vector/vector.go b/pkg/vector/vector.go index 9c449ec19..11cf4bf05 100644 --- a/pkg/vector/vector.go +++ b/pkg/vector/vector.go @@ -11,7 +11,7 @@ type Vector []float64 type Vectors = []Vector // NewVector creates a new vector from the given values. -func NewVector(values interface{}) (Vector, error) { +func NewVector(values any) (Vector, error) { switch v := values.(type) { case []uint8: return uint8ToVector(v), nil diff --git a/scripts/tools/swaggerfix/main.go b/scripts/tools/swaggerfix/main.go index 96d5d58d5..bfe5c407f 100644 --- a/scripts/tools/swaggerfix/main.go +++ b/scripts/tools/swaggerfix/main.go @@ -1,52 +1,51 @@ package main import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" + "encoding/json" + "fmt" + "io/ioutil" + "os" ) func main() { - if len(os.Args) < 2 { - fmt.Fprintln(os.Stderr, "usage: swaggerfix ") - os.Exit(2) - } - path := os.Args[1] - b, err := ioutil.ReadFile(path) - if err != nil { - fmt.Fprintln(os.Stderr, "read:", err) - os.Exit(1) - } - var doc map[string]interface{} - if err := json.Unmarshal(b, &doc); err != nil { - fmt.Fprintln(os.Stderr, "parse:", err) - os.Exit(1) - } - // Traverse to definitions.time.Duration - defs, _ := doc["definitions"].(map[string]interface{}) - if defs == nil { - fmt.Fprintln(os.Stderr, "no definitions in swagger file") - os.Exit(1) - } - td, _ := defs["time.Duration"].(map[string]interface{}) - if td == nil { - fmt.Fprintln(os.Stderr, "no time.Duration schema found; nothing to do") - os.Exit(0) - } - // Remove unstable enums and varnames to ensure deterministic output. - delete(td, "enum") - delete(td, "x-enum-varnames") - defs["time.Duration"] = td - doc["definitions"] = defs - out, err := json.MarshalIndent(doc, "", " ") - if err != nil { - fmt.Fprintln(os.Stderr, "marshal:", err) - os.Exit(1) - } - if err := ioutil.WriteFile(path, out, 0644); err != nil { - fmt.Fprintln(os.Stderr, "write:", err) - os.Exit(1) - } + if len(os.Args) < 2 { + fmt.Fprintln(os.Stderr, "usage: swaggerfix ") + os.Exit(2) + } + path := os.Args[1] + b, err := ioutil.ReadFile(path) + if err != nil { + fmt.Fprintln(os.Stderr, "read:", err) + os.Exit(1) + } + var doc map[string]any + if err := json.Unmarshal(b, &doc); err != nil { + fmt.Fprintln(os.Stderr, "parse:", err) + os.Exit(1) + } + // Traverse to definitions.time.Duration + defs, _ := doc["definitions"].(map[string]any) + if defs == nil { + fmt.Fprintln(os.Stderr, "no definitions in swagger file") + os.Exit(1) + } + td, _ := defs["time.Duration"].(map[string]any) + if td == nil { + fmt.Fprintln(os.Stderr, "no time.Duration schema found; nothing to do") + os.Exit(0) + } + // Remove unstable enums and varnames to ensure deterministic output. + delete(td, "enum") + delete(td, "x-enum-varnames") + defs["time.Duration"] = td + doc["definitions"] = defs + out, err := json.MarshalIndent(doc, "", " ") + if err != nil { + fmt.Fprintln(os.Stderr, "marshal:", err) + os.Exit(1) + } + if err := ioutil.WriteFile(path, out, 0644); err != nil { + fmt.Fprintln(os.Stderr, "write:", err) + os.Exit(1) + } } -