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 ¶
- func RegisterRoutes(api *httpx.API, svc contracts.Service)
- func RemoveBlob(storage contracts.Storage) events.Subscription
- type Local
- type Reconcile
- type Service
- func (s *Service) Delete(ctx context.Context, tx db.Tx[db.Tenant], id uuid.UUID) (*contracts.File, error)
- func (s *Service) Open(ctx context.Context, tx db.Tx[db.Tenant], id uuid.UUID, anonymous bool) (*contracts.File, io.ReadCloser, error)
- func (s *Service) Upload(ctx context.Context, open contracts.Tx, up contracts.Upload) (*contracts.File, error)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func RegisterRoutes ¶
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 ¶
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 ¶
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.
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 ¶
NewReconcile prepares the sweep. every replaces the daily schedule for a test.
func (*Reconcile) Jobs ¶
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 ¶
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.