internal

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Sep 3, 2026 License: Apache-2.0 Imports: 28 Imported by: 0

Documentation

Overview

Package internal is every implementation of the file module. Nothing outside modules/file can import it, which is the compiler enforcing idea 3.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RegisterRoutes

func RegisterRoutes(api *httpx.API, svc contracts.Service)

RegisterRoutes mounts the six routes a file has.

There is no rest.Spec, and the reason is one sentence: a Spec's create route takes a JSON body, and a file arrives as bytes. The list and the read below are the two Spec routes that would have made sense, written out; the create is a multipart upload, the update does not exist because a file's bytes are what they are, and the delete has an event to publish that the generic one could not carry.

func RemoveBlob

func RemoveBlob(storage contracts.Storage) events.Subscription

RemoveBlob is this module's subscription to its own file.deleted, and the reason that event exists.

Removing the bytes cannot happen in the transaction that removed the row: a file delete is not something a rollback can undo, so a transaction that failed after it would leave the row back and the bytes gone — a download that fails forever. An event is the only thing in this architecture that is delivered exactly after a commit, so the row's removal publishes where the bytes are and this handler removes them.

It is idempotent because kit/events claims each delivery, and idempotent again because a key with nothing at it is not an error: a redelivery after a half-finished attempt finishes it instead of failing forever.

Types

type Local

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

Local is contracts.Storage on the filesystem, which is what a laptop, a single machine and a mounted volume all are. The implementations that speak to an object store live outside this repository.

A key is a UUID, checked here as well as generated here, and that is the whole path-traversal argument: there is no caller-supplied component in a key to escape a directory with, and the check makes that true of a caller this package cannot see. The first two characters are a subdirectory, because a directory with a million entries is slow in every filesystem worth naming.

func NewLocal

func NewLocal(dir string) *Local

NewLocal returns storage under dir. The directory is created when the first blob is written rather than here, so constructing this in a composition touches no disk.

func (*Local) Delete

func (l *Local) Delete(_ context.Context, k string) error

Delete removes the bytes. A key with nothing at it is not an error: the worker that calls this retries, and a retry that failed because the first attempt succeeded would never stop.

func (*Local) Get

func (l *Local) Get(_ context.Context, k string) (io.ReadCloser, error)

Get opens the bytes, or ErrNoBlob when there are none.

func (*Local) Keys

func (l *Local) Keys(_ context.Context, before time.Time) ([]string, error)

Keys is contracts.Lister on the filesystem: every blob written before before.

It walks the two-character directories Put creates and reads each entry's modification time, which is when the upload finished writing it.

func (*Local) Put

func (l *Local) Put(_ context.Context, k string, r io.Reader, _ int64) error

Put writes the bytes, refusing a key that already exists: a key is minted per upload, so a collision is a bug rather than a replacement. size is ignored — a filesystem needs no length up front.

type Reconcile

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

Reconcile removes blobs no row references.

It is the one piece of periodic work this module has, and it exists because of the ordering the module chose on purpose: an upload writes the bytes first, so a transaction that fails afterwards leaves bytes nobody references. That is the right trade — the other order leaves a row whose download fails forever — and this is the cost of it, swept up once a day.

It is not jobs.PerTenant, and the reason is the shape of the problem rather than a preference. PerTenant walks the tenants and hands each one a context; a blob on disk carries no tenant, and the ones this job is looking for are exactly the ones no row names, so there is no tenant to walk to them from. The sweep therefore starts at the store — Lister enumerates the keys — and asks the database which of them are known, under system access, because the question crosses every tenant by construction: a key belonging to another tenant's row is a key that must not be deleted, and a tenant-scoped query could not see it.

func NewReconcile

func NewReconcile(storage contracts.Storage, every time.Duration) *Reconcile

NewReconcile prepares the sweep. every replaces the daily schedule for a test.

func (*Reconcile) Jobs

func (r *Reconcile) Jobs() []jobs.Job

Jobs is the daily sweep, or none: a Storage that cannot enumerate what it holds cannot be reconciled, and a job that could never do anything is worse than no job because it appears in the schedule.

func (*Reconcile) Use

func (r *Reconcile) Use(token tenancy.SystemToken)

Use hands over the capability that opens a cross-tenant transaction. It is called from Module.Routes, which is the one moment the kernel offers a token — a job is constructed before the API exists — and the job does nothing without one, which is a boot-order mistake rather than a request-time condition, so it says so in the log rather than deleting anything.

type Service

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

Service is the file lifecycle. It has three fields, unlike the services in the other modules: the bytes have to go somewhere, and how large one upload may be and how much disk one tenant may hold are a deployment's decisions rather than this module's.

func NewService

func NewService(storage contracts.Storage, max, quota int64) *Service

NewService takes the storage the bytes go to, the largest upload this deployment accepts, and the disk one tenant may fill. module.go constructs it.

func (*Service) Delete

func (s *Service) Delete(ctx context.Context, tx db.Tx[db.Tenant], id uuid.UUID) (*contracts.File, error)

Delete removes the row and says where the bytes are. See contracts.Service.

func (*Service) Open

func (s *Service) Open(ctx context.Context, tx db.Tx[db.Tenant], id uuid.UUID, anonymous bool) (*contracts.File, io.ReadCloser, error)

Open is the row and its bytes. See contracts.Service.

func (*Service) Upload

func (s *Service) Upload(ctx context.Context, open contracts.Tx, up contracts.Upload) (*contracts.File, error)

Upload streams the bytes into storage while hashing and counting them, then opens the caller's transaction and writes the row. See contracts.Service.

Jump to

Keyboard shortcuts

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