thumbnail

package
v0.116.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 9, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package thumbnail generates and caches low-resolution JPEG thumbnails for images on disk. Used by the file-browser endpoints to keep image grids snappy — the original raw bytes are too large to render dozens at once on a phone over a Tailscale link.

Cache layout: <TempDir>/kojo/thumb/<sha256(absPath|modtimeNano|filesize|size)>.jpg

The cache key includes modtime AND file size so an in-place edit invalidates the thumbnail automatically even when the editor preserves modtime (rare, but cheap insurance). Cache entries older than cacheMaxAge are swept by PurgeOld — invoke that periodically from the owning process; otherwise the OS reclaims tmpdir on reboot.

Index

Constants

View Source
const (
	// MinSize / MaxSize bound the requested thumbnail edge (the longer
	// side after preserve-aspect scaling).
	MinSize = 16
	MaxSize = 1024
	// DefaultSize is what handlers use when no `size` query arg is given.
	// Chosen for a 2x-DPR 150-px grid tile.
	DefaultSize = 256
)

Variables

View Source
var (
	ErrUnsupportedFormat = errors.New("unsupported image format")
	// ErrSourceTooLarge guards against decode-bomb DoS. Callers should map
	// to HTTP 413.
	ErrSourceTooLarge = errors.New("source image exceeds size budget")
)

ErrUnsupportedFormat is returned when the source can't be decoded as an image. Callers should map this to HTTP 415.

Functions

func CacheDir

func CacheDir() string

CacheDir returns the directory where thumbnails are stored. Created on demand by Generate.

func ClampSize

func ClampSize(size int) int

ClampSize returns size clamped to [MinSize, MaxSize], or DefaultSize when size <= 0. Handlers should call this before passing user input through.

func Generate

func Generate(srcPath string, size int) (string, error)

Generate returns the absolute path to a cached JPEG thumbnail for srcPath at the requested size (longer edge). srcPath must already be a validated, absolute path — this package does no access control.

Returns ErrUnsupportedFormat when the source can't be decoded, or ErrSourceTooLarge when the source exceeds the byte/pixel budget.

func IsSupportedExt

func IsSupportedExt(ext string) bool

IsSupportedExt reports whether ext (lower-case, leading dot) is a format we know how to thumbnail. Handlers can short-circuit obvious non-images before opening the file. Match the formats handled in decodeImage.

func PurgeOld

func PurgeOld() (int, error)

PurgeOld removes cache entries whose mtime is older than cacheMaxAge. Safe to call concurrently with Generate — the worst case is that a just-written entry races a sweep and gets re-generated on the next request. Returns the number of files removed.

func ServeHTTP added in v0.101.2

func ServeHTTP(w http.ResponseWriter, r *http.Request, srcPath string, size int) error

ServeHTTP serves a thumbnail with Cache-Control: no-cache so the browser stores the body but revalidates via ETag on every request (→ 304 when unchanged). Use for sources whose URL is stable across content changes (avatars, blob thumbs without a version query).

Callers provide the raw HTTP request so http.ServeFile can handle If-None-Match → 304. Returns nil on success; a pre-stream failure is returned as *HTTPError carrying the mapped status (ErrUnsupportedFormat → 415, ErrSourceTooLarge → 413, os.IsNotExist → 404, else 500). The wrapped error keeps errors.Is working against the package sentinels.

func StartPurger

func StartPurger(done <-chan struct{})

StartPurger runs PurgeOld every purgeInterval until ctx-style done channel closes. Callers should invoke this once at server start. The function returns immediately; the loop runs in its own goroutine.

Types

type HTTPError added in v0.110.0

type HTTPError struct {
	Status  int
	Message string
	// contains filtered or unexported fields
}

HTTPError carries an HTTP status and message for a pre-stream failure so that callers can deliver the error in their own format (e.g. the server's JSON envelope) instead of this package writing text/plain itself. It wraps the underlying error so errors.Is against the package sentinels still matches. Once streaming has begun the serving path can no longer change the status code, so late failures are logged rather than returned as HTTPError.

func NewHTTPError added in v0.110.0

func NewHTTPError(status int, message string, err error) *HTTPError

NewHTTPError builds an HTTPError with the given status and message, wrapping err so errors.Is against the package sentinels keeps matching. Pass err=nil when the failure has no underlying error to preserve (e.g. a pre-check).

func (*HTTPError) Error added in v0.110.0

func (e *HTTPError) Error() string

func (*HTTPError) Unwrap added in v0.110.0

func (e *HTTPError) Unwrap() error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL