Some checks failed
CI / Test (Go 1.24.x, ubuntu-latest) (push) Successful in 1m53s
CI / Code Quality (push) Failing after 26s
CI / Security Scan (push) Failing after 11s
CI / Test Coverage (push) Successful in 1m13s
CI / Benchmarks (push) Failing after 10m22s
CI / Build CLI (push) Failing after 8s
Benchmarks / Run Benchmarks (push) Failing after 10m13s
Release / Test (push) Successful in 55s
Release / Build (amd64, darwin, ) (push) Failing after 12s
Release / Build (amd64, linux, ) (push) Failing after 6s
Release / Build (amd64, windows, .exe) (push) Failing after 12s
Release / Build (arm64, darwin, ) (push) Failing after 12s
Release / Build (arm64, linux, ) (push) Failing after 12s
Release / Release (push) Has been skipped
CI / Test (Go 1.24.x, macos-latest) (push) Has been cancelled
CI / Test (Go 1.24.x, windows-latest) (push) Has been cancelled
Move hosting from GitHub to private Gitea instance.
104 lines
2.7 KiB
Go
104 lines
2.7 KiB
Go
package engine
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"gitea.dockr.co/kev/go-jdenticon/internal/util"
|
|
)
|
|
|
|
// Generate creates an identicon with the specified hash and size
|
|
// This method includes caching and singleflight to prevent duplicate work
|
|
func (g *Generator) Generate(ctx context.Context, hash string, size float64) (*Icon, error) {
|
|
// Basic validation
|
|
if hash == "" {
|
|
return nil, fmt.Errorf("jdenticon: engine: generation failed: hash cannot be empty")
|
|
}
|
|
if size <= 0 {
|
|
return nil, fmt.Errorf("jdenticon: engine: generation failed: invalid size: %f", size)
|
|
}
|
|
|
|
// Check icon size limits
|
|
if g.maxIconSize > 0 && int(size) > g.maxIconSize {
|
|
return nil, fmt.Errorf("jdenticon: engine: generation failed: icon size %d exceeds maximum allowed size %d", int(size), g.maxIconSize)
|
|
}
|
|
|
|
// Validate hash format
|
|
if !util.IsValidHash(hash) {
|
|
return nil, fmt.Errorf("jdenticon: engine: generation failed: invalid hash format: %s", hash)
|
|
}
|
|
|
|
// Check for context cancellation before proceeding
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Generate cache key
|
|
key := g.cacheKey(hash, size)
|
|
|
|
// Check cache first (with read lock)
|
|
g.mu.RLock()
|
|
if cached, ok := g.cache.Get(key); ok {
|
|
g.mu.RUnlock()
|
|
g.metrics.recordHit()
|
|
return cached, nil
|
|
}
|
|
g.mu.RUnlock()
|
|
|
|
// Use singleflight to prevent multiple concurrent generations for the same key
|
|
result, err, _ := g.sf.Do(key, func() (interface{}, error) {
|
|
// Check cache again inside singleflight (another goroutine might have populated it)
|
|
g.mu.RLock()
|
|
if cached, ok := g.cache.Get(key); ok {
|
|
g.mu.RUnlock()
|
|
g.metrics.recordHit()
|
|
return cached, nil
|
|
}
|
|
g.mu.RUnlock()
|
|
|
|
// Generate the icon
|
|
icon, err := g.generateIcon(ctx, hash, size)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Store in cache (with write lock)
|
|
g.mu.Lock()
|
|
g.cache.Add(key, icon)
|
|
g.mu.Unlock()
|
|
|
|
g.metrics.recordMiss()
|
|
return icon, nil
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return result.(*Icon), nil
|
|
}
|
|
|
|
// GenerateWithoutCache creates an identicon without using cache
|
|
// This method is useful for testing or when caching is not desired
|
|
func (g *Generator) GenerateWithoutCache(ctx context.Context, hash string, size float64) (*Icon, error) {
|
|
// Basic validation
|
|
if hash == "" {
|
|
return nil, fmt.Errorf("jdenticon: engine: generation failed: hash cannot be empty")
|
|
}
|
|
if size <= 0 {
|
|
return nil, fmt.Errorf("jdenticon: engine: generation failed: invalid size: %f", size)
|
|
}
|
|
|
|
// Validate hash format
|
|
if !util.IsValidHash(hash) {
|
|
return nil, fmt.Errorf("jdenticon: engine: generation failed: invalid hash format: %s", hash)
|
|
}
|
|
|
|
// Check for context cancellation
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return g.generateIcon(ctx, hash, size)
|
|
}
|