* Load an image file and create the cached result.
(
imageFile: File,
cacheKey: string,
generation: number
)
| 104 | * Load an image file and create the cached result. |
| 105 | */ |
| 106 | async function loadFromFile( |
| 107 | imageFile: File, |
| 108 | cacheKey: string, |
| 109 | generation: number |
| 110 | ): Promise<T | null> { |
| 111 | // Helper to clean up load tracking. |
| 112 | // Only decrement activeLoads if we're still in the same generation. |
| 113 | // If clear() was called (generation changed), it already reset activeLoads to 0. |
| 114 | const cleanup = () => { |
| 115 | if (generation === state.cacheGeneration) { |
| 116 | state.activeLoads--; |
| 117 | state.loadingPromises.delete(cacheKey); |
| 118 | processQueue(); |
| 119 | } |
| 120 | }; |
| 121 | |
| 122 | try { |
| 123 | // Skip images that have previously failed to decode |
| 124 | if (hasImageFailed(cacheKey)) { |
| 125 | cleanup(); |
| 126 | return null; |
| 127 | } |
| 128 | |
| 129 | if (generation !== state.cacheGeneration) { |
| 130 | // Don't call cleanup - clear() already reset state |
| 131 | return null; |
| 132 | } |
| 133 | |
| 134 | const cached = state.cache.get(cacheKey); |
| 135 | if (cached) { |
| 136 | cleanup(); |
| 137 | return cached; |
| 138 | } |
| 139 | |
| 140 | let bitmap: ImageBitmap; |
| 141 | try { |
| 142 | const decodedBitmap = await createImageBitmapWithTimeout(imageFile, DECODE_TIMEOUT); |
| 143 | bitmap = await resizeImageBitmapToMaxSizeWithTimeout(decodedBitmap, maxSize, DECODE_TIMEOUT); |
| 144 | } catch (bitmapErr) { |
| 145 | // Mark as failed so we don't retry (causes OOM with many failures) |
| 146 | const failedCount = markImageFailed(cacheKey); |
| 147 | // Only log first 20 failures to avoid console spam |
| 148 | if (shouldLogDecodeFailure(failedCount)) { |
| 149 | appLogger.warn(`[decode] Failed: "${cacheKey}" (${(imageFile.size / 1024).toFixed(0)} KB) -`, bitmapErr); |
| 150 | } else if (shouldLogDecodeFailureSuppression(failedCount)) { |
| 151 | appLogger.warn(`... suppressing further decode errors (${failedCount} images failed)`); |
| 152 | } |
| 153 | cleanup(); |
| 154 | return null; |
| 155 | } |
| 156 | |
| 157 | if (generation !== state.cacheGeneration) { |
| 158 | bitmap.close(); |
| 159 | // Don't call cleanup - clear() already reset state |
| 160 | return null; |
| 161 | } |
| 162 | |
| 163 | // Return promise that cleans up AFTER idle processing completes |
no test coverage detected