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
- Variables
- func BuiltinDraws(name, mime string) bool
- func IsNoEngineSkip(reason string) bool
- func NoteForReason(reason string) string
- func NoteOf(t *model.Thumbnail) string
- func OnlyOfficeDraws(name, mime string) bool
- func ParseAppReason(reason string) (code, app string, limit int64, ok bool)
- func ParseNoTool(reason string) (kind string, ok bool)
- func ParseOfficeReason(reason string) (code, what string, tries int, ok bool)
- func ParseSkip(reason string) (code string, limit int64)
- func SourceSig(n *model.Node) string
- type AppThumbs
- type Attempt
- type Capabilities
- type NodeGetter
- type OfficeDrawer
- type OfficeError
- type OfficeLimits
- type OfficePage
- type Pipeline
- func (p *Pipeline) Assess(n *model.Node, t *model.Thumbnail, now time.Time) Verdict
- func (p *Pipeline) AttachApps(a AppThumbs)
- func (p *Pipeline) AttachBody(b *filebody.Resolver)
- func (p *Pipeline) AttachOffice(d OfficeDrawer)
- func (p *Pipeline) AttachSettings(g dbsetting.Getter)
- func (p *Pipeline) AttachStorage(id int64, drv storage.Driver)
- func (p *Pipeline) CacheDir() string
- func (p *Pipeline) CachePath(nodeID int64) string
- func (p *Pipeline) FolderPreviews() bool
- func (p *Pipeline) Forget(ctx context.Context, nodeID int64)
- func (p *Pipeline) ForgetSettings()
- func (p *Pipeline) GenerateThumb(ctx context.Context, node *model.Node) (out error)
- func (p *Pipeline) OfficeLimits() OfficeLimits
- func (p *Pipeline) ReapOrphans(ctx context.Context) (ReapResult, error)
- func (p *Pipeline) RefitOversized(ctx context.Context) (RefitResult, error)
- func (p *Pipeline) RunReaper(ctx context.Context, interval time.Duration)
- func (p *Pipeline) SVGLimits() SVGLimits
- func (p *Pipeline) Wanted(n *model.Node, t *model.Thumbnail, sel Selection) bool
- type ReapResult
- type RefitResult
- type Refresher
- func (r *Refresher) Consider(n *model.Node, t *model.Thumbnail) Verdict
- func (r *Refresher) ConsiderAll(ctx context.Context, nodes []*model.Node)
- func (r *Refresher) Len() int
- func (r *Refresher) OnRendered(fn func(n *model.Node))
- func (r *Refresher) Queued(id int64) bool
- func (r *Refresher) Run(ctx context.Context, workers int)
- func (r *Refresher) SetClock(now func() time.Time)
- type SVGLimits
- type Selection
- type SignStore
- type Signer
- type Verdict
Constants ¶
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).
const ( SkipArchiveEncrypted = "archive_encrypted" SkipArchiveTooLarge = "archive_too_large" )
Skip reasons an archive can record.
const ( NoteCorrupt = "corrupt" NoteEncrypted = "encrypted" NoteTooLarge = "too_large" )
The notes a listing may carry.
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.
const ( OfficeTransient = "transient" OfficeCorrupt = "corrupt" OfficePassword = "password" OfficeTooLarge = "too_large" OfficeEncrypted = "encrypted" OfficeUnconfigured = "unconfigured" )
The classes of an OfficeError.
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).
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).
const CheckerSize = 8
The transparency checkerboard: squares of CheckerSize pixels, the top-left one CheckerLight.
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.
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).
const SkipNoHandler = "no_handler"
SkipNoHandler is the reason of a file whose kind has handlers, every one of them switched off by the administrator.
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.
const SkipNoTool = "no_tool"
SkipNoTool is the reason prefix for a kind whose program is missing.
Variables ¶
var ( CheckerLight = color.RGBA{0xCC, 0xCC, 0xCC, 0xFF} CheckerDark = color.RGBA{0x99, 0x99, 0x99, 0xFF} )
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.
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.
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".
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
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
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
NoteForReason is the note of one recorded reason (noteTable).
func NoteOf ¶ added in v0.50.0
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
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
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
ParseNoTool reads a "no_tool:<kind>" reason back.
func ParseOfficeReason ¶ added in v0.50.0
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.
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
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
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
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
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
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
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
AttachApps wires the apps that draw thumbnails. Nil (the runtime is off): filex's own drawer alone, as before 0.50.
func (*Pipeline) AttachBody ¶
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
AttachSettings wires the settings store the SVG limits are read from.
func (*Pipeline) AttachStorage ¶
AttachStorage registers a Driver for a storage ID — needed because the pipeline reads source bytes from the originating storage.
func (*Pipeline) FolderPreviews ¶ added in v0.50.0
FolderPreviews reports whether folders show their pictures (the folder card, and the list on a resting pointer): FolderPreviewsSetting, cached.
func (*Pipeline) Forget ¶
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 ¶
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 ¶
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.
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
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
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) OnRendered ¶ added in v0.50.0
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
Queued reports whether a render of id is waiting or running.
type SVGLimits ¶ added in v0.50.0
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).
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 (*Signer) Query ¶
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) Verify ¶
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.
Source Files
¶
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. |