package engine import ( "context" "fmt" "github.com/ungluedlabs/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) }