The new package internal/imagerequest holds the request type, its two parts Source and Transform, the Format and Fit types, the dimension and fit checks, the /v1/image/ path parsing and the variant key; it imports no other package of pixa. imgcache.ParsedURL, imgcache.ImageRequest, signature.Request, imageprocessor.Request, the second Size, format and fit types and the conversions between them are gone. The image processor takes a Transform only. encurl keeps a private struct with the token's CBOR field names, so tokens made before still decode. VariantKey moves with the function that makes it. Tests follow the moved and renamed code; the tests of the removed conversions go with them. Model: opus-5-5
This commit is contained in:
+34
-28
@@ -17,6 +17,7 @@ import (
|
||||
|
||||
lru "github.com/hashicorp/golang-lru/v2"
|
||||
"sneak.berlin/go/pixa/internal/httpfetcher"
|
||||
"sneak.berlin/go/pixa/internal/imagerequest"
|
||||
)
|
||||
|
||||
// Cache errors.
|
||||
@@ -111,7 +112,7 @@ type Cache struct {
|
||||
// metaCache holds the content types of the variants most recently
|
||||
// stored or served, so a hit does not read the variant's .meta file.
|
||||
// It never stands in for the variant file, which is always opened.
|
||||
metaCache *lru.Cache[VariantKey, string]
|
||||
metaCache *lru.Cache[imagerequest.VariantKey, string]
|
||||
|
||||
// contentLocks serializes StoreSource and evictSourceBlob per
|
||||
// content hash, closing the race window between an eviction's row
|
||||
@@ -153,7 +154,7 @@ func newCache(
|
||||
log = slog.Default()
|
||||
}
|
||||
|
||||
metaCache, err := lru.New[VariantKey, string](metaCacheSize)
|
||||
metaCache, err := lru.New[imagerequest.VariantKey, string](metaCacheSize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create variant content type cache: %w", err)
|
||||
}
|
||||
@@ -223,7 +224,7 @@ func newCache(
|
||||
// LookupResult contains the result of a cache lookup.
|
||||
type LookupResult struct {
|
||||
Hit bool
|
||||
CacheKey VariantKey
|
||||
CacheKey imagerequest.VariantKey
|
||||
ContentType string
|
||||
SizeBytes int64
|
||||
CacheStatus CacheStatus
|
||||
@@ -231,8 +232,10 @@ type LookupResult struct {
|
||||
|
||||
// Lookup checks if a processed variant exists on disk. Hits touch the
|
||||
// variant's LRU timestamp; a disabled cache always misses.
|
||||
func (c *Cache) Lookup(ctx context.Context, req *ImageRequest) (*LookupResult, error) {
|
||||
cacheKey := CacheKey(req)
|
||||
func (c *Cache) Lookup(
|
||||
ctx context.Context, req *imagerequest.Request,
|
||||
) (*LookupResult, error) {
|
||||
cacheKey := imagerequest.CacheKey(req)
|
||||
|
||||
// Check variant storage directly - no DB needed for cache hits
|
||||
if !c.disabled && c.variants.Exists(cacheKey) {
|
||||
@@ -257,7 +260,9 @@ func (c *Cache) Lookup(ctx context.Context, req *ImageRequest) (*LookupResult, e
|
||||
// variant's .meta file and is then kept in metaCache. A variant with
|
||||
// no .meta file is served as application/octet-stream, which is not
|
||||
// kept.
|
||||
func (c *Cache) GetVariant(cacheKey VariantKey) (io.ReadCloser, int64, string, error) {
|
||||
func (c *Cache) GetVariant(
|
||||
cacheKey imagerequest.VariantKey,
|
||||
) (io.ReadCloser, int64, string, error) {
|
||||
if c.disabled {
|
||||
return nil, 0, "", ErrNotFound
|
||||
}
|
||||
@@ -282,7 +287,7 @@ func (c *Cache) GetVariant(cacheKey VariantKey) (io.ReadCloser, int64, string, e
|
||||
// disabled cache it is a no-op returning an empty hash.
|
||||
func (c *Cache) StoreSource(
|
||||
ctx context.Context,
|
||||
req *ImageRequest,
|
||||
req *imagerequest.Request,
|
||||
content io.Reader,
|
||||
result *httpfetcher.FetchResult,
|
||||
) (ContentHash, error) {
|
||||
@@ -322,7 +327,7 @@ func (c *Cache) StoreSource(
|
||||
}
|
||||
|
||||
// Store in database
|
||||
pathHash := HashPath(req.SourcePath + "?" + req.SourceQuery)
|
||||
pathHash := HashPath(req.Source.Path + "?" + req.Source.Query)
|
||||
|
||||
headersJSON, err := json.Marshal(result.Headers)
|
||||
if err != nil {
|
||||
@@ -349,7 +354,7 @@ func (c *Cache) StoreSource(
|
||||
content_type = excluded.content_type,
|
||||
response_headers = excluded.response_headers,
|
||||
fetched_at = CURRENT_TIMESTAMP
|
||||
`, req.SourceHost, req.SourcePath, req.SourceQuery, pathHash,
|
||||
`, req.Source.Host, req.Source.Path, req.Source.Query, pathHash,
|
||||
contentHash, httpStatusOK, result.ContentType, string(headersJSON))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to insert source metadata: %w", err)
|
||||
@@ -367,7 +372,8 @@ func (c *Cache) StoreSource(
|
||||
// accounting insert is best-effort (the startup reconciliation pass
|
||||
// adopts any variant file that misses its accounting row).
|
||||
func (c *Cache) StoreVariant(
|
||||
ctx context.Context, cacheKey VariantKey, content io.Reader, contentType string,
|
||||
ctx context.Context, cacheKey imagerequest.VariantKey,
|
||||
content io.Reader, contentType string,
|
||||
) error {
|
||||
if c.disabled {
|
||||
return nil
|
||||
@@ -403,7 +409,7 @@ func (c *Cache) StoreVariant(
|
||||
// if not. Hits touch the blob's LRU timestamp; a disabled cache always
|
||||
// reports no cached source.
|
||||
func (c *Cache) LookupSource(
|
||||
ctx context.Context, req *ImageRequest,
|
||||
ctx context.Context, req *imagerequest.Request,
|
||||
) (ContentHash, string, error) {
|
||||
if c.disabled {
|
||||
return "", "", nil
|
||||
@@ -414,7 +420,7 @@ func (c *Cache) LookupSource(
|
||||
err := c.db.QueryRowContext(ctx, `
|
||||
SELECT content_hash, content_type FROM source_metadata
|
||||
WHERE source_host = ? AND source_path = ? AND source_query = ?
|
||||
`, req.SourceHost, req.SourcePath, req.SourceQuery).Scan(&hashStr, &contentType)
|
||||
`, req.Source.Host, req.Source.Path, req.Source.Query).Scan(&hashStr, &contentType)
|
||||
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return "", "", nil
|
||||
@@ -438,7 +444,7 @@ func (c *Cache) LookupSource(
|
||||
|
||||
// StoreNegative stores a negative cache entry for a failed fetch.
|
||||
func (c *Cache) StoreNegative(
|
||||
ctx context.Context, req *ImageRequest, statusCode int, errMsg string,
|
||||
ctx context.Context, req *imagerequest.Request, statusCode int, errMsg string,
|
||||
) error {
|
||||
expiresAt := time.Now().UTC().Add(c.config.NegativeTTL)
|
||||
|
||||
@@ -452,7 +458,7 @@ func (c *Cache) StoreNegative(
|
||||
error_message = excluded.error_message,
|
||||
fetched_at = CURRENT_TIMESTAMP,
|
||||
expires_at = excluded.expires_at
|
||||
`, req.SourceHost, req.SourcePath, req.SourceQuery, statusCode, errMsg, expiresAt)
|
||||
`, req.Source.Host, req.Source.Path, req.Source.Query, statusCode, errMsg, expiresAt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to insert negative cache: %w", err)
|
||||
}
|
||||
@@ -462,14 +468,14 @@ func (c *Cache) StoreNegative(
|
||||
|
||||
// GetSourceMetadataID returns the source metadata ID for a request.
|
||||
func (c *Cache) GetSourceMetadataID(
|
||||
ctx context.Context, req *ImageRequest,
|
||||
ctx context.Context, req *imagerequest.Request,
|
||||
) (int64, error) {
|
||||
var id int64
|
||||
|
||||
err := c.db.QueryRowContext(ctx, `
|
||||
SELECT id FROM source_metadata
|
||||
WHERE source_host = ? AND source_path = ? AND source_query = ?
|
||||
`, req.SourceHost, req.SourcePath, req.SourceQuery).Scan(&id)
|
||||
`, req.Source.Host, req.Source.Path, req.Source.Query).Scan(&id)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get source metadata ID: %w", err)
|
||||
}
|
||||
@@ -665,7 +671,7 @@ func (c *Cache) readHitAndMissCounts(ctx context.Context, stats *CacheStats) err
|
||||
// application/octet-stream and keeps nothing, so metaCache only ever
|
||||
// holds a type read from a .meta file or passed to StoreVariant.
|
||||
func (c *Cache) loadVariantWithMeta(
|
||||
cacheKey VariantKey,
|
||||
cacheKey imagerequest.VariantKey,
|
||||
) (io.ReadCloser, int64, string, error) {
|
||||
reader, size, contentType, err := c.variants.LoadWithMeta(cacheKey)
|
||||
if err != nil {
|
||||
@@ -685,15 +691,15 @@ func (c *Cache) loadVariantWithMeta(
|
||||
// A failure is logged and is otherwise non-fatal; the metadata is in the
|
||||
// database.
|
||||
func (c *Cache) writeMetadataSidecar(
|
||||
req *ImageRequest,
|
||||
req *imagerequest.Request,
|
||||
pathHash PathHash,
|
||||
contentHash ContentHash,
|
||||
result *httpfetcher.FetchResult,
|
||||
) {
|
||||
meta := &SourceMetadata{
|
||||
Host: req.SourceHost,
|
||||
Path: req.SourcePath,
|
||||
Query: req.SourceQuery,
|
||||
Host: req.Source.Host,
|
||||
Path: req.Source.Path,
|
||||
Query: req.Source.Query,
|
||||
ContentHash: string(contentHash),
|
||||
StatusCode: result.StatusCode,
|
||||
ContentType: result.ContentType,
|
||||
@@ -704,16 +710,16 @@ func (c *Cache) writeMetadataSidecar(
|
||||
RemoteAddr: result.RemoteAddr,
|
||||
}
|
||||
|
||||
err := c.srcMetadata.Store(req.SourceHost, pathHash, meta)
|
||||
err := c.srcMetadata.Store(req.Source.Host, pathHash, meta)
|
||||
if err != nil {
|
||||
c.log.Warn("failed to write metadata sidecar",
|
||||
"host", req.SourceHost, "path_hash", pathHash, "error", err)
|
||||
"host", req.Source.Host, "path_hash", pathHash, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// touchVariant updates the LRU timestamp of a variant, best-effort:
|
||||
// a failed touch only makes the entry look colder to eviction.
|
||||
func (c *Cache) touchVariant(ctx context.Context, cacheKey VariantKey) {
|
||||
func (c *Cache) touchVariant(ctx context.Context, cacheKey imagerequest.VariantKey) {
|
||||
_, err := c.db.ExecContext(ctx, `
|
||||
UPDATE variant_content SET last_accessed_at = CURRENT_TIMESTAMP
|
||||
WHERE cache_key = ?
|
||||
@@ -739,14 +745,14 @@ func (c *Cache) touchSourceContent(ctx context.Context, contentHash ContentHash)
|
||||
|
||||
// checkNegativeCache checks if a request is in the negative cache.
|
||||
func (c *Cache) checkNegativeCache(
|
||||
ctx context.Context, req *ImageRequest,
|
||||
ctx context.Context, req *imagerequest.Request,
|
||||
) (bool, error) {
|
||||
var expiresAt time.Time
|
||||
|
||||
err := c.db.QueryRowContext(ctx, `
|
||||
SELECT expires_at FROM negative_cache
|
||||
WHERE source_host = ? AND source_path = ? AND source_query = ?
|
||||
`, req.SourceHost, req.SourcePath, req.SourceQuery).Scan(&expiresAt)
|
||||
`, req.Source.Host, req.Source.Path, req.Source.Query).Scan(&expiresAt)
|
||||
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return false, nil
|
||||
@@ -762,10 +768,10 @@ func (c *Cache) checkNegativeCache(
|
||||
_, err = c.db.ExecContext(ctx, `
|
||||
DELETE FROM negative_cache
|
||||
WHERE source_host = ? AND source_path = ? AND source_query = ?
|
||||
`, req.SourceHost, req.SourcePath, req.SourceQuery)
|
||||
`, req.Source.Host, req.Source.Path, req.Source.Query)
|
||||
if err != nil {
|
||||
c.log.Warn("failed to delete expired negative cache entry",
|
||||
"host", req.SourceHost, "path", req.SourcePath, "error", err)
|
||||
"host", req.Source.Host, "path", req.Source.Path, "error", err)
|
||||
}
|
||||
|
||||
return false, nil
|
||||
|
||||
Reference in New Issue
Block a user