loadOrCache returns a cached blob for the given image path, using the image cache. Cache key is image path only, so the same source serves all requested sizes. If load is non-nil and blob is nil, load is called inside the singleflight to fetch the blob, deduplicating network requests across concurre
( blob *imagor.Blob, imagePath string, n int, load imagor.LoadFunc, )
| 51 | // Returns (nil, nil, nil) if cache is disabled or the source is animated |
| 52 | // (multi-page structure cannot be preserved in the cache). |
| 53 | func (v *Processor) loadOrCache( |
| 54 | blob *imagor.Blob, imagePath string, n int, load imagor.LoadFunc, |
| 55 | ) (*imagor.Blob, *imagor.Blob, error) { |
| 56 | if v.cache == nil { |
| 57 | return nil, nil, nil |
| 58 | } |
| 59 | |
| 60 | // Fast path: cache hit — return immediately without singleflight overhead. |
| 61 | if memBlob, ok := v.cache.Get(imagePath); ok { |
| 62 | return memBlob, nil, nil |
| 63 | } |
| 64 | |
| 65 | // Deduplicate concurrent cache misses for the same image path. |
| 66 | result, err, _ := v.cacheSF.Do(imagePath, func() (any, error) { |
| 67 | // Re-check after acquiring the singleflight group. |
| 68 | if memBlob, ok := v.cache.Get(imagePath); ok { |
| 69 | return &loadOrCacheResult{memBlob: memBlob}, nil |
| 70 | } |
| 71 | |
| 72 | // If blob not provided, fetch it inside the singleflight so concurrent |
| 73 | // cache misses share a single network request. |
| 74 | if blob == nil { |
| 75 | if load == nil { |
| 76 | return &loadOrCacheResult{}, nil |
| 77 | } |
| 78 | var err error |
| 79 | blob, err = load(imagePath) |
| 80 | if err != nil { |
| 81 | return nil, err |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | // Decode at maxW×maxH with SizeDown. Fresh context so the VipsSource |
| 86 | // is released immediately after serialization. |
| 87 | decodeCtx := withContext(context.Background()) |
| 88 | |
| 89 | img, err := v.NewThumbnail(decodeCtx, blob, v.CacheMaxWidth, v.CacheMaxHeight, |
| 90 | vips.InterestingNone, vips.SizeDown, n, 1, 0) |
| 91 | if err != nil { |
| 92 | contextDone(decodeCtx) |
| 93 | return nil, err |
| 94 | } |
| 95 | |
| 96 | // Animated source: multi-page structure cannot be preserved in the cache. |
| 97 | // Return the original blob so the caller can serve it directly. |
| 98 | if img.Height() != img.PageHeight() { |
| 99 | img.Close() |
| 100 | contextDone(decodeCtx) |
| 101 | return &loadOrCacheResult{origBlob: blob}, nil |
| 102 | } |
| 103 | |
| 104 | // Static image: serialize to Go-owned bytes, release libvips resources. |
| 105 | // Storage format is controlled by CacheFormat: |
| 106 | // BlobTypeWEBP → WebpsaveBuffer (lossy, smaller memory, slight quality difference) |
| 107 | // BlobTypePNG → PngsaveBuffer (lossless, smaller memory, pixel-identical) |
| 108 | // default → WriteToMemory (raw pixels, fastest hit, most memory) |
| 109 | imgW, imgH := img.Width(), img.PageHeight() |
| 110 |
no test coverage detected