Help us shape the future of Go! Take the 2026 Go Developer Survey

thumb

package
v0.56.0 Latest Latest
Warning

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

Go to latest
Published: Oct 11, 2026 License: MIT Imports: 54 Imported by: 0

Documentation

Overview

Package thumb generates and serves thumbnail images for nodes.

The pipeline is dispatcher-based: GenerateThumb asks the file's chain of handlers (filex's own drawer, the OnlyOffice document server for office documents, apps) in order, and the first that draws wins. filex's own drawer routes by the file's type (image / video / audio / pdf / text / archive). Each writes a JPEG to the cache and the thumbnails row records what happened.

Generators that require external binaries (ffmpeg, gs or pdftoppm, ImageMagick) detect availability up-front via the capability package and skip with the reason when they are not present (notool.go); office documents need OnlyOffice configured (office.go).

Index

Constants

View Source
const (
	SkipAppFailed   = "app_failed"
	SkipAppTimeout  = "app_timeout"
	SkipAppTooLarge = "app_too_large"
)

The reasons an app's handler records (the row's error when it was the first in the chain, and its entry in `attempts` either way).

View Source
const (
	SkipArchiveEncrypted = "archive_encrypted"
	SkipArchiveTooLarge  = "archive_too_large"
)

Skip reasons an archive can record.

View Source
const (
	NoteCorrupt   = "corrupt"
	NoteEncrypted = "encrypted"
	NoteTooLarge  = "too_large"
)

The notes a listing may carry.

View Source
const (
	ToolVideo = "video" // FFmpeg
	ToolAudio = "audio" // FFmpeg
	ToolPDF   = "pdf"   // Ghostscript or poppler
	// ToolOffice: OnlyOffice is not configured (office.go). Not a program on
	// this machine: the document server filex is configured with.
	ToolOffice = "office"
	ToolHEIF   = "heif" // ImageMagick with HEIC/AVIF support (libheif)
	// ToolHEICCodec: ImageMagick is there and cannot decode a HEIC photo -
	// libheif without its HEVC decoder plugin (libheif-plugin-libde265 on
	// Debian and Ubuntu, libheif-libde265 on Alpine). Measured, not assumed:
	// enginebin.HEIC draws a sample. AVIF is not affected (another decoder).
	ToolHEICCodec = "heic_codec"
)

The kinds a missing program can leave undrawn, and what each needs.

View Source
const (
	OfficeTransient    = "transient"
	OfficeCorrupt      = "corrupt"
	OfficePassword     = "password"
	OfficeTooLarge     = "too_large"
	OfficeEncrypted    = "encrypted"
	OfficeUnconfigured = "unconfigured"
)

The classes of an OfficeError.

View Source
const (
	SkipOfficeCorrupt  = "oo_corrupt"
	SkipOfficePassword = "oo_password"
	SkipOfficeTooLarge = "oo_too_large"
	SkipOfficeRetry    = "oo_retry"
)

The reasons an office document's row records (see the table above).

View Source
const (
	SkipSVGTooLarge = "svg_too_large"
	SkipSVGTimeout  = "svg_timeout"
)

Skip reasons for an SVG over a limit, recorded as "<code>:<limit>" so the row says which limit it was over (bytes, or milliseconds).

View Source
const CheckerSize = 8

The transparency checkerboard: squares of CheckerSize pixels, the top-left one CheckerLight.

View Source
const DefaultURLTTL = 24 * time.Hour

DefaultURLTTL is how long a stamped thumbnail URL stays valid.

⚠ It matches the `Cache-Control: private, max-age=86400` the endpoint sends: a signature that expired before the browser cache did would produce a thumbnail that renders from cache on one machine and 403s on another, which is the kind of difference nobody can reproduce.

View Source
const SigningKeySetting = "thumb_signing_key"

SigningKeySetting is the settings row holding the HMAC key. Generated on first use and then stable — rotating it only invalidates outstanding URLs, it never locks anybody out (authenticated callers do not need one).

View Source
const SkipNoHandler = "no_handler"

SkipNoHandler is the reason of a file whose kind has handlers, every one of them switched off by the administrator.

View Source
const SkipNoSVGEngine = "svg: no engine available"

SkipNoSVGEngine is the reason a row records when an SVG could not be drawn because no SVG engine was available. Assess reads it back: once an engine is there, those rows are drawn.

View Source
const SkipNoTool = "no_tool"

SkipNoTool is the reason prefix for a kind whose program is missing.

Variables

View Source
var (
	CheckerLight = color.RGBA{0xCC, 0xCC, 0xCC, 0xFF}
	CheckerDark  = color.RGBA{0x99, 0x99, 0x99, 0xFF}
)
View Source
var (
	// OfficeMaxMBSetting: the largest document sent to the document server.
	// 25 MB: the measured documents drew in under a second up to a few MB, a
	// 13 MB sheet took 15 s; past this a thumbnail is not worth holding the
	// one converter the editors share. The document server's own download
	// limit is 100 MB by default (FileConverter.converter.maxDownloadBytes).
	OfficeMaxMBSetting = dbsetting.IntSpec{
		Key: "thumbs.office_max_mb", EnvVar: "FILEX_THUMBS_OFFICE_MAX_MB",
		Default: 25, Min: 1, Max: 100, Unit: "MB",
	}
	// OfficeSlotsSetting: how many documents one filex process has the
	// document server convert at once. 1: a Community Edition server runs one
	// converter, shared with the editors.
	OfficeSlotsSetting = dbsetting.IntSpec{
		Key: "thumbs.office_slots", EnvVar: "FILEX_THUMBS_OFFICE_SLOTS",
		Default: 1, Min: 1, Max: 4, Unit: "conversions",
	}
)

The two settings an office thumbnail is drawn under (Settings, and Admin → Tools → Thumbnail repair), and the time each file has.

View Source
var (
	// FolderPreviewsSetting: folders show their pictures (the folder card
	// drawn with up to three prints rising out of it, and the list of what
	// it holds on a resting pointer). On by default; an administrator turns
	// it off in Settings or on the repair tab, and the grid keeps the classic
	// folder cards.
	FolderPreviewsSetting = dbsetting.BoolSpec{
		Key: "thumbs.folder_previews", EnvVar: "FILEX_THUMBS_FOLDER_PREVIEWS", Default: true,
	}
	SVGMaxMBSetting = dbsetting.IntSpec{
		Key: "thumbs.svg_max_mb", EnvVar: "FILEX_THUMBS_SVG_MAX_MB",
		Default: 5, Min: 1, Max: 64, Unit: "MB",
	}
	SVGTimeoutSetting = dbsetting.IntSpec{
		Key: "thumbs.svg_timeout_seconds", EnvVar: "FILEX_THUMBS_SVG_TIMEOUT",
		Default: 10, Min: 1, Max: 120, Unit: "seconds",
	}
)

The two limits an SVG thumbnail is drawn under (GitHub #79: "set a limit, e.g. 5 MB; I have very complex SVGs and the preview takes forever"). Administrators change them in Settings and in Admin → Tools → Thumbnail repair; a file over either is `skipped` with the limit it hit, shows its SVG icon, is listed with the reason on the repair tab, and is drawn again once the limit is raised (Assess and Wanted read the limit back).

Why 5 MB and 10 s: of 54 real-world SVGs measured (docs/thumbnails.md, Design notes: SVG) the largest was 1.1 MB and the slowest took 0.93 s in the built-in engine — ten times under the time limit, with room for a machine several times slower. A file past either is not a picture anyone waits for a thumbnail of: 14 MB of machine-made paths took over twenty seconds, and every second a render takes is a second one of the few background workers is not drawing the next file.

View Source
var ErrSkipped = errors.New("thumb: skipped")

ErrSkipped is returned when no generator applies to the node — caller should mark the node's thumb state as "skipped" rather than "failed".

View Source
var Fix = Selection{Stale: true, Failed: true, Skipped: true}

Fix is the selection "repair what is wrong": missing, stale, failed and skipped.

Functions

func BuiltinDraws added in v0.50.0

func BuiltinDraws(name, mime string) bool

BuiltinDraws reports whether filex draws a file of this name and type itself: an image (SVG and HEIC included), a video, an audio file, a PDF, a text, an archive it lists. It does not say the tool is installed: a kind whose program is missing is still the built-in drawer's, which skips it with the reason (no_tool). Office documents are not: the OnlyOffice document server draws them (OnlyOfficeDraws, office.go).

func IsNoEngineSkip added in v0.50.0

func IsNoEngineSkip(reason string) bool

IsNoEngineSkip reports whether a skip reason is "no SVG engine" (this release's wording or 0.49's).

func NoteForReason added in v0.50.0

func NoteForReason(reason string) string

NoteForReason is the note of one recorded reason (noteTable).

func NoteOf added in v0.50.0

func NoteOf(t *model.Thumbnail) string

NoteOf is the note a file's thumbnail row gives it: "" when it has a picture, is being drawn, or has none for a reason that is not the file's.

func OnlyOfficeDraws added in v0.50.0

func OnlyOfficeDraws(name, mime string) bool

OnlyOfficeDraws reports whether the document server draws a thumbnail of a file of this name and type (whether OnlyOffice is configured is not this function's question).

⚠ `.key` is a Keynote presentation only when its content is a package (a zip, as the sniff calls it): the same extension names TLS private keys and licence keys, and those are never handed to anybody to "draw".

func ParseAppReason added in v0.50.0

func ParseAppReason(reason string) (code, app string, limit int64, ok bool)

ParseAppReason reads an app's reason back: its code (app_failed, app_timeout, app_too_large), the app, and the limit it records (0 for app_failed). ok is false for any other reason.

func ParseNoTool added in v0.50.0

func ParseNoTool(reason string) (kind string, ok bool)

ParseNoTool reads a "no_tool:<kind>" reason back.

func ParseOfficeReason added in v0.50.0

func ParseOfficeReason(reason string) (code, what string, tries int, ok bool)

ParseOfficeReason reads an office reason back: its code (oo_corrupt, oo_password, oo_retry; oo_too_large is ParseSkip's), what failed, and for oo_retry the tries so far. ok is false for any other reason.

func ParseSkip added in v0.50.0

func ParseSkip(reason string) (code string, limit int64)

ParseSkip reads a skip reason written by skipReason back: its code and the limit it records (svg_too_large, svg_timeout, oo_too_large; an oo_too_large of 0 is the document server's own limit). A reason of another shape comes back as ("", 0).

func SourceSig added in v0.50.0

func SourceSig(n *model.Node) string

SourceSig is the signature a row records for the content it was drawn from: the node's ContentFingerprint, the one definition the search index uses as well.

Types

type AppThumbs added in v0.50.0

type AppThumbs interface {
	// ThumbChain is the handlers that draw a file of this name and type, in
	// the order they are asked (assoc.Service.Chain, capability thumbnail).
	ThumbChain(ctx context.Context, name, mime string) assoc.Chain
	// DrawThumbnail asks the app behind h to draw one file: a PNG or a JPEG,
	// or an *assoc.DrawError.
	DrawThumbnail(ctx context.Context, h assoc.Handler, req assoc.DrawRequest) ([]byte, error)
	// AppLimits are an app's limits now: the largest file it is sent, and the
	// time it has per file (to tell a row skipped for one of them that the
	// limit was raised). ok is false for an app that draws nothing now.
	AppLimits(app string) (maxBytes int64, timeout time.Duration, ok bool)
}

AppThumbs is what the pipeline asks about thumbnails drawn by apps.

type Attempt added in v0.50.0

type Attempt struct {
	H string `json:"h"`
	R string `json:"r"`
}

Attempt is one handler asked about a file, as the row's `attempts` keeps it: the handler (assoc.Handler.Key: `builtin`, `app:<name>@<version>`) and what it answered ("ok", or the reason).

func ParseAttempts added in v0.50.0

func ParseAttempts(s string) []Attempt

ParseAttempts reads a row's `attempts` back; nil for a row from before 0.50 (or one that cannot be read).

type Capabilities

type Capabilities struct {
	Image bool // always true (Go stdlib + bundled imaging)
	Video bool // ffmpeg present in PATH
	Audio bool // ffmpeg present (same binary handles audio waveform)
	PDF   bool // ghostscript or pdftoppm present
	// Office documents are not a capability of this process: OnlyOffice
	// draws them while it is configured (AttachOffice, office.go).
	// SVG: an SVG can be drawn. Always true since 0.50, when the built-in
	// engine (thumb/svgwasm) arrived; false only in tests that ask for it.
	SVG bool
	// RSVG: rsvg-convert is on PATH, the fallback for a file the built-in
	// engine could not draw.
	RSVG bool
	// HEIF: ImageMagick is installed, which reads HEIC, HEIF and AVIF
	// through libheif (the full image carries imagemagick-heic).
	//
	// ⚠ Installed, not able: whether it DECODES a HEIC is measured separately
	// (enginebin.HEIC, a sample drawn once), and a HEIC it cannot decode is
	// skipped as "no_tool:heic_codec" (notool.go).
	HEIF bool
}

Capabilities indicates which thumbnail backends are available at runtime.

type NodeGetter added in v0.50.0

type NodeGetter interface {
	GetNode(ctx context.Context, id int64) (*model.Node, error)
}

NodeGetter is what the refresher reads a node back through before drawing it: the listing's copy may be minutes old by then.

type OfficeDrawer added in v0.50.0

type OfficeDrawer interface {
	// Ready reports whether OnlyOffice is configured right now. From memory
	// (the configuration is cached): Assess asks it for every listed office
	// file.
	Ready(ctx context.Context) bool
	// DrawPage asks the document server for a PNG of the document's first
	// page. A failure is an *OfficeError; any other error is transient.
	DrawPage(ctx context.Context, page OfficePage) ([]byte, error)
}

OfficeDrawer is what the pipeline asks to draw an office document's first page (the server's adapter over internal/onlyoffice).

type OfficeError added in v0.50.0

type OfficeError struct {
	Class string
	What  string
	Err   error
}

OfficeError is why the document server did not draw a document. What is a short name of the failure for the row ("ds-3", "http-502", "net", "timeout", "origin", ...): never an address, never a secret.

func (*OfficeError) Error added in v0.50.0

func (e *OfficeError) Error() string

func (*OfficeError) Unwrap added in v0.50.0

func (e *OfficeError) Unwrap() error

type OfficeLimits added in v0.50.0

type OfficeLimits struct {
	MaxBytes int64
	Slots    int
}

OfficeLimits is what an office document is drawn under right now.

func DefaultOfficeLimits added in v0.50.0

func DefaultOfficeLimits() OfficeLimits

DefaultOfficeLimits apply with no settings store.

func ResolveOfficeLimits added in v0.50.0

func ResolveOfficeLimits(ctx context.Context, g dbsetting.Getter) OfficeLimits

ResolveOfficeLimits reads the two settings.

func (OfficeLimits) String added in v0.50.0

func (l OfficeLimits) String() string

String is for logs.

type OfficePage added in v0.50.0

type OfficePage struct {
	NodeID    int64
	StorageID int64
	// Name is the file's name; its extension is the type the document server
	// is told.
	Name string
	// ContentSig is the content's fingerprint (SourceSig), and Attempt counts
	// the tries at it from 1: both are part of the document server's key, so
	// a new version or a retry is never answered from its cache.
	ContentSig string
	Attempt    int
	// MaxBytes is the largest answer accepted.
	MaxBytes int64
}

OfficePage is one document to draw.

type Pipeline

type Pipeline struct {
	// contains filtered or unexported fields
}

Pipeline coordinates thumbnail generation.

func New

func New(store db.Store, cacheDir string, caps Capabilities) *Pipeline

New constructs a Pipeline.

func (*Pipeline) Assess added in v0.50.0

func (p *Pipeline) Assess(n *model.Node, t *model.Thumbnail, now time.Time) Verdict

Assess decides whether n's thumbnail row t (nil: no row) should be drawn.

⚠⚠ It reads only what the caller already holds: the catalogue row a listing or the storage sync just read, and the thumbnail row next to it. No byte of the file and no request to the backend, because it runs on every listing. The table it implements, with the reasons, is in docs/thumbnails.md (Design notes → Freshness).

func (*Pipeline) AttachApps added in v0.50.0

func (p *Pipeline) AttachApps(a AppThumbs)

AttachApps wires the apps that draw thumbnails. Nil (the runtime is off): filex's own drawer alone, as before 0.50.

func (*Pipeline) AttachBody

func (p *Pipeline) AttachBody(b *filebody.Resolver)

AttachBody wires the byte-source resolver, so a file that is still being transferred gets its thumbnail from the staged bytes instead of failing against a driver that does not have the object yet.

func (*Pipeline) AttachOffice added in v0.50.0

func (p *Pipeline) AttachOffice(d OfficeDrawer)

AttachOffice wires the document server. Nil: no office thumbnails (each office file is skipped as no_tool:office).

func (*Pipeline) AttachSettings added in v0.50.0

func (p *Pipeline) AttachSettings(g dbsetting.Getter)

AttachSettings wires the settings store the SVG limits are read from.

func (*Pipeline) AttachStorage

func (p *Pipeline) AttachStorage(id int64, drv storage.Driver)

AttachStorage registers a Driver for a storage ID — needed because the pipeline reads source bytes from the originating storage.

func (*Pipeline) CacheDir added in v0.50.0

func (p *Pipeline) CacheDir() string

CacheDir is the directory the cached JPEGs live in.

func (*Pipeline) CachePath

func (p *Pipeline) CachePath(nodeID int64) string

CachePath returns the disk path where a thumb is stored for node ID.

func (*Pipeline) FolderPreviews added in v0.50.0

func (p *Pipeline) FolderPreviews() bool

FolderPreviews reports whether folders show their pictures (the folder card, and the list on a resting pointer): FolderPreviewsSetting, cached.

func (*Pipeline) Forget

func (p *Pipeline) Forget(ctx context.Context, nodeID int64)

Forget removes one node's cached thumbnail — the JPEG and the row.

Call it where a node is destroyed permanently (trash purge, retention expiry, a hard delete on a driver that cannot preserve bytes). It is NOT for a soft delete: a trashed file is restorable and keeps its thumbnail.

Best-effort by design: a thumbnail is a cache, and failing to remove one must never fail the deletion it is attached to. Every failure is logged.

func (*Pipeline) ForgetSettings added in v0.50.0

func (p *Pipeline) ForgetSettings()

ForgetSettings drops the cached settings: the settings page calls it after a change, so the next file is drawn, and the next folder listed, under the new ones.

func (*Pipeline) GenerateThumb

func (p *Pipeline) GenerateThumb(ctx context.Context, node *model.Node) (out error)

GenerateThumb dispatches based on node MIME and updates the thumbnails row. Idempotent — safe to call repeatedly.

Falls back to the file extension when `node.Mime` is empty — this is the common case for files discovered by sync's driver.List, where most drivers don't populate the mime field. Without the fallback every synced file got `state=skipped` and the pipeline never produced a thumbnail for the demo fixtures.

A panic while drawing is that file's failure (survive), never the process's: every caller - the upload's goroutine, the backfill and repair walkers, an ops job - comes through here.

func (*Pipeline) OfficeLimits added in v0.50.0

func (p *Pipeline) OfficeLimits() OfficeLimits

OfficeLimits is what an office document is drawn under right now (cached with the other thumbnail settings, svglimits.go).

func (*Pipeline) ReapOrphans

func (p *Pipeline) ReapOrphans(ctx context.Context) (ReapResult, error)

ReapOrphans deletes cached thumbnails whose node no longer exists.

See the file comment for the four conditions a file has to meet. Returns the tally so the caller can log it; an error means the pass was ABANDONED and nothing was deleted.

func (*Pipeline) RefitOversized

func (p *Pipeline) RefitOversized(ctx context.Context) (RefitResult, error)

RefitOversized scales down every cached thumbnail wider than thumbMaxWidth. See the comment above for which files it touches. An error means the pass stopped early (the directory could not be read, or ctx ended); files already rewritten stay rewritten.

func (*Pipeline) RunReaper

func (p *Pipeline) RunReaper(ctx context.Context, interval time.Duration)

RunReaper reconciles once at boot and then on every tick until ctx is done. The boot pass is followed by one RefitOversized pass (refit.go): the files it rewrites can only come from a version before 0.41.0, so once per start is enough and the ticks stay the cheap orphan sweep they always were.

interval <= 0 disables it entirely (FILEX_THUMBS_SWEEP_INTERVAL=0) — a kill switch for an operator who would rather run `filex thumb` maintenance by hand than have the server touch the cache directory at all.

⚠ One log line per pass, including the quiet ones. The staged-upload sweeper learned this the expensive way: a sweeper that only speaks when it deletes something is indistinguishable from a sweeper that has stopped running.

func (*Pipeline) SVGLimits added in v0.50.0

func (p *Pipeline) SVGLimits() SVGLimits

SVGLimits is what an SVG is drawn under right now.

func (*Pipeline) Wanted added in v0.50.0

func (p *Pipeline) Wanted(n *model.Node, t *model.Thumbnail, sel Selection) bool

Wanted reports whether a repair run with selection sel draws n, whose row is t. Unlike Assess it has no loop guard and no idea of "somebody is drawing it": an administrator asked for exactly this.

type ReapResult

type ReapResult struct {
	Scanned int   // candidate files inspected
	Removed int   // files deleted
	Freed   int64 // bytes reclaimed
	Kept    int   // files whose node still exists (live, or trashed and restorable)
	Skipped int   // files too fresh to judge, or unparseable names
}

ReapResult is what one reconciliation pass did.

type RefitResult

type RefitResult struct {
	Scanned  int   // cache files whose dimensions were read and judged
	Refitted int   // files rewritten at thumbnail size
	Freed    int64 // bytes the rewrites saved
	Failed   int   // oversized files that could not be rewritten (left as they were)
	Skipped  int   // not ours, too fresh, not a readable JPEG, or too large to decode safely
}

RefitResult is what one re-fit pass did.

type Refresher added in v0.50.0

type Refresher struct {
	// contains filtered or unexported fields
}

Refresher draws, in the background, the thumbnails a listing or the storage sync found missing or stale (Assess).

⚠⚠ Consider never makes its caller wait. It runs inside every listing, and a listing is the one request a person is looking at while it loads. The queue is bounded and held in memory: at most one entry per node, dropped when full. That is enough because the catalogue is the durable record of what is stale — the next listing that shows the file asks again. The persistent queue would add a database write per file to the listing and make nothing more durable (docs/thumbnails.md, Design notes).

func NewRefresher added in v0.50.0

func NewRefresher(p *Pipeline, nodes NodeGetter, capacity int) *Refresher

NewRefresher builds a refresher with room for capacity waiting files.

func (*Refresher) Consider added in v0.50.0

func (r *Refresher) Consider(n *model.Node, t *model.Thumbnail) Verdict

Consider assesses n's thumbnail row t and, when it should be drawn, queues a copy of n. It returns the verdict. Nil-safe: a nil refresher (thumbnails off) leaves everything.

func (*Refresher) ConsiderAll added in v0.50.0

func (r *Refresher) ConsiderAll(ctx context.Context, nodes []*model.Node)

ConsiderAll is Consider for many files at once, reading their rows in one batched query: what the storage sync hands over after a batch of entries drifted (sync.Worker.AttachThumbs). filex's own folders are left alone.

func (*Refresher) Len added in v0.50.0

func (r *Refresher) Len() int

Len is the number of renders waiting or running.

func (*Refresher) OnRendered added in v0.50.0

func (r *Refresher) OnRendered(fn func(n *model.Node))

OnRendered is called after every render the refresher ran, whatever it ended in. The server announces a derived change to the file's folder from here, so an explorer showing it reloads and gets the new picture.

func (*Refresher) Queued added in v0.50.0

func (r *Refresher) Queued(id int64) bool

Queued reports whether a render of id is waiting or running.

func (*Refresher) Run added in v0.50.0

func (r *Refresher) Run(ctx context.Context, workers int)

Run draws queued files with the given number of workers until ctx ends.

func (*Refresher) SetClock added in v0.50.0

func (r *Refresher) SetClock(now func() time.Time)

SetClock replaces the clock Assess is asked with (tests).

type SVGLimits added in v0.50.0

type SVGLimits struct {
	MaxBytes int64
	Timeout  time.Duration
}

SVGLimits is what an SVG is drawn under right now.

func DefaultSVGLimits added in v0.50.0

func DefaultSVGLimits() SVGLimits

DefaultSVGLimits is what applies with no settings store (tests, the CLI before the store is wired).

func ResolveSVGLimits added in v0.50.0

func ResolveSVGLimits(ctx context.Context, g dbsetting.Getter) SVGLimits

ResolveSVGLimits reads the two settings.

type Selection added in v0.50.0

type Selection struct {
	// All draws every file in scope ("Rebuild").
	All bool
	// Stale draws a ready picture of other content, and a failure or a skip
	// recorded on other content.
	Stale bool
	// Failed and Skipped draw those rows whatever content they were on.
	Failed  bool
	Skipped bool
}

Selection is what a repair run draws: Admin → Tools → Thumbnail repair and `filex thumb backfill` (server.BackfillThumbs). A file with no row, or one left pending, is always drawn; a skip whose engine has arrived too.

type SignStore

type SignStore interface {
	GetSetting(ctx context.Context, key string) (string, error)
	UpsertSetting(ctx context.Context, key, value string) error
}

SignStore is the slice of db.Store the signer needs. Narrow on purpose so the signer is testable without a database.

type Signer

type Signer struct {
	// contains filtered or unexported fields
}

Signer mints and verifies thumbnail URL signatures.

The zero value is not usable; a nil *Signer is, and it is the fail-CLOSED state: Query returns no signature and Verify refuses everything, so an install that never wired one simply requires an authenticated caller.

func NewSigner

func NewSigner(store SignStore, ttl time.Duration) *Signer

NewSigner returns a signer over store. ttl <= 0 means DefaultURLTTL.

func (*Signer) Query

func (s *Signer) Query(id int64) string

Query returns the `exp=…&sig=…` fragment for node id, or "" when no signature can be produced (nil signer, or the key could not be loaded — callers must then fall back to authenticated fetching rather than emit an unsigned URL that would only 401).

⚠ The expiry is QUANTIZED to a bucket rather than being `now + ttl`. An expiry that moved on every listing would change the URL string on every listing, and the URL string is the cache key both for the browser's HTTP cache and for useThumbs' object-URL cache — every folder refresh would re-download every thumbnail. Quantizing makes the URL stable within a window at the cost of up to one bucket of extra lifetime.

func (*Signer) TTL

func (s *Signer) TTL() time.Duration

TTL reports the configured lifetime of a stamped URL.

func (*Signer) Verify

func (s *Signer) Verify(id int64, exp, sig string) bool

Verify reports whether (exp, sig) is a live signature for node id.

A mismatch triggers ONE reload of the key from the settings row before the refusal: another process (a second replica, or an operator rotating the row) may have changed it since this process cached it, and a stale cache would otherwise reject signatures the deployment is currently minting.

type Verdict added in v0.50.0

type Verdict uint8

Verdict is what Assess says about one file's thumbnail.

const (
	// Leave means the row is as good as it will get: fresh, or failed/skipped
	// on this very content.
	Leave Verdict = iota
	// Render means the file should be drawn (again): it never was, its content
	// changed since, or what stopped it last time is gone.
	Render
)

func (Verdict) String added in v0.50.0

func (v Verdict) String() string

Directories

Path Synopsis
Package svgwasm draws SVG files with resvg compiled to WebAssembly, run in the pure-Go wazero runtime: the SVG engine filex carries itself, so an SVG gets a thumbnail on every install (the full image, :slim, the bare binary) without rsvg-convert and without cgo.
Package svgwasm draws SVG files with resvg compiled to WebAssembly, run in the pure-Go wazero runtime: the SVG engine filex carries itself, so an SVG gets a thumbnail on every install (the full image, :slim, the bare binary) without rsvg-convert and without cgo.

Jump to

Keyboard shortcuts

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