package storage import ( "context" "errors" "io" ) // Errors of the media store. var ( // ErrNoSuchMedia means no media, or no thumbnail, is stored under the // given name. ErrNoSuchMedia = errors.New("no such media") // ErrMediaExists is CreateMedia's answer for a media ID that is taken. ErrMediaExists = errors.New("media already exists") // ErrMediaUploaded is CompleteUpload's answer for media whose content // has already been uploaded: content, once stored, never changes. ErrMediaUploaded = errors.New("media content already uploaded") ) // Media is the metadata of one piece of content: uploaded here, or fetched // from the server it was uploaded to and kept. type Media struct { // Origin is the server name in the mxc:// URI: this server for an // upload, another for cached remote media. Origin string MediaID string // ContentType is the Content-Type the content was uploaded with, // parameters included. ContentType string // UploadName is the file name the uploader gave, or "". UploadName string // Size is the content's length in bytes. Size int64 // SHA256 names the content in the blob store: its SHA-256, in // lower-case hex. Empty while the media is pending. SHA256 string // UserID is the uploader; "" for remote media. UserID string CreatedAtMS int64 // Pending is set for an ID handed out by /create whose content has // not been uploaded yet. Pending media expires at ExpiresAtMS. Pending bool ExpiresAtMS int64 } // MediaContent is what CompleteUpload fills in on pending media. type MediaContent struct { ContentType string UploadName string Size int64 SHA256 string } // Thumbnail is one generated (or, for remote media, fetched) thumbnail. type Thumbnail struct { Origin string MediaID string // Width, Height and Method are the size the thumbnail was made for — // one of the configured sizes — not the size of the image itself, // which for "scale" keeps the original's aspect ratio. Width int Height int Method string ContentType string Size int64 SHA256 string CreatedAtMS int64 } // MediaStore is the mediaapi store: the metadata of local and cached // remote media and of their thumbnails. The content itself is in a // BlobStore. type MediaStore interface { // CreateMedia records media: an upload, a pending upload or a remote // fetch. A taken (origin, media ID) is ErrMediaExists. CreateMedia(ctx context.Context, m Media) error // Media reads one media record, pending or not. Media(ctx context.Context, origin, mediaID string) (Media, error) // CompleteUpload stores the content of pending media and clears its // pending flag. It is ErrNoSuchMedia for an unknown ID and // ErrMediaUploaded for media that is not pending. CompleteUpload(ctx context.Context, origin, mediaID string, content MediaContent) error // PendingUploads counts a user's pending media that has not expired // by nowMS. PendingUploads(ctx context.Context, userID string, nowMS int64) (int, error) // DeleteExpiredUploads removes pending media that expired before // nowMS, reporting how many. DeleteExpiredUploads(ctx context.Context, nowMS int64) (int, error) // Thumbnail reads the thumbnail made for one size. Thumbnail(ctx context.Context, origin, mediaID string, width, height int, method string) (Thumbnail, error) // StoreThumbnail records a thumbnail, replacing one stored for the // same size. StoreThumbnail(ctx context.Context, t Thumbnail) error // MediaUsage totals what a user has uploaded to this server. Pending // reservations count for nothing until their content arrives. MediaUsage(ctx context.Context, origin, userID string) (MediaUsage, error) // MediaQuota returns the quota the admin API set for a user, and // whether one is set. MediaQuota(ctx context.Context, userID string) (int64, bool, error) // SetMediaQuota sets a user's quota; 0 is no limit. SetMediaQuota(ctx context.Context, userID string, maxBytes int64) error // DeleteMediaQuota returns a user to the configured default. DeleteMediaQuota(ctx context.Context, userID string) error } // MediaUsage is what one user's uploads add up to. type MediaUsage struct { Count int Bytes int64 } // ErrNoSuchBlob means the blob store holds nothing under a hash. var ErrNoSuchBlob = errors.New("no such blob") // ErrBlobTooLarge means content passed the limit it was stored with. var ErrBlobTooLarge = errors.New("content is larger than allowed") // BlobInfo describes stored content. type BlobInfo struct { // SHA256 is the lower-case hex SHA-256 of the content, and its name in // the store. SHA256 string Size int64 } // BlobStore keeps media content, addressed by its SHA-256. The same bytes // uploaded twice are stored once. type BlobStore interface { // Put stores what r yields, up to limit bytes; more is // ErrBlobTooLarge and stores nothing. Put(ctx context.Context, r io.Reader, limit int64) (BlobInfo, error) // Open reads stored content. Open(ctx context.Context, sha256 string) (io.ReadSeekCloser, error) // Delete removes stored content. Deleting what is not there is not an // error. Delete(ctx context.Context, sha256 string) error } // BlobProvider is implemented by a storage driver that brings its own // BlobStore. PocketBase will (PLAN.md section 4: its filesystem // abstraction puts media on local disk or S3); a driver that does not is // given the local filesystem store. type BlobProvider interface { Blobs() BlobStore }