2019-06-11 16:23:20 +00:00
|
|
|
package handler
|
2015-02-01 13:57:57 +00:00
|
|
|
|
|
|
|
import (
|
2019-06-11 14:16:02 +00:00
|
|
|
"context"
|
2015-02-01 13:57:57 +00:00
|
|
|
"io"
|
|
|
|
)
|
|
|
|
|
|
|
|
type MetaData map[string]string
|
|
|
|
|
2022-03-01 23:36:49 +00:00
|
|
|
// FileInfo contains information about a single upload resource.
|
2015-02-01 13:57:57 +00:00
|
|
|
type FileInfo struct {
|
2022-03-01 23:36:49 +00:00
|
|
|
// ID is the unique identifier of the upload resource.
|
2015-03-23 16:58:13 +00:00
|
|
|
ID string
|
2015-02-01 15:17:56 +00:00
|
|
|
// Total file size in bytes specified in the NewUpload call
|
2015-02-03 18:01:22 +00:00
|
|
|
Size int64
|
2018-04-23 21:07:51 +00:00
|
|
|
// Indicates whether the total file size is deferred until later
|
|
|
|
SizeIsDeferred bool
|
2015-02-01 15:17:56 +00:00
|
|
|
// Offset in bytes (zero-based)
|
2015-02-01 13:57:57 +00:00
|
|
|
Offset int64
|
|
|
|
MetaData MetaData
|
2015-02-17 13:19:56 +00:00
|
|
|
// Indicates that this is a partial upload which will later be used to form
|
|
|
|
// a final upload by concatenation. Partial uploads should not be processed
|
|
|
|
// when they are finished since they are only incomplete chunks of files.
|
2015-02-17 14:44:12 +00:00
|
|
|
IsPartial bool
|
2015-02-17 13:19:56 +00:00
|
|
|
// Indicates that this is a final upload
|
2015-02-17 14:44:12 +00:00
|
|
|
IsFinal bool
|
2015-02-17 13:19:56 +00:00
|
|
|
// If the upload is a final one (see IsFinal) this will be a non-empty
|
|
|
|
// ordered slice containing the ids of the uploads of which the final upload
|
|
|
|
// will consist after concatenation.
|
|
|
|
PartialUploads []string
|
2019-08-19 07:29:56 +00:00
|
|
|
// Storage contains information about where the data storage saves the upload,
|
|
|
|
// for example a file path. The available values vary depending on what data
|
|
|
|
// store is used. This map may also be nil.
|
|
|
|
Storage map[string]string
|
2019-05-26 19:56:51 +00:00
|
|
|
|
|
|
|
// stopUpload is the cancel function for the upload's context.Context. When
|
|
|
|
// invoked it will interrupt the writes to DataStore#WriteChunk.
|
|
|
|
stopUpload context.CancelFunc
|
|
|
|
}
|
|
|
|
|
|
|
|
// StopUpload interrupts an running upload from the server-side. This means that
|
|
|
|
// the current request body is closed, so that the data store does not get any
|
|
|
|
// more data. Furthermore, a response is sent to notify the client of the
|
|
|
|
// interrupting and the upload is terminated (if supported by the data store),
|
|
|
|
// so the upload cannot be resumed anymore.
|
2022-03-01 23:36:49 +00:00
|
|
|
// TODO: Allow passing in a HTTP Response
|
2019-05-26 19:56:51 +00:00
|
|
|
func (f FileInfo) StopUpload() {
|
|
|
|
if f.stopUpload != nil {
|
|
|
|
f.stopUpload()
|
|
|
|
}
|
2015-02-01 13:57:57 +00:00
|
|
|
}
|
|
|
|
|
2019-08-24 13:14:51 +00:00
|
|
|
type Upload interface {
|
2015-02-01 15:17:56 +00:00
|
|
|
// Write the chunk read from src into the file specified by the id at the
|
|
|
|
// given offset. The handler will take care of validating the offset and
|
|
|
|
// limiting the size of the src to not overflow the file's size. It may
|
2015-11-19 14:18:26 +00:00
|
|
|
// return an os.ErrNotExist which will be interpreted as a 404 Not Found.
|
2015-02-01 15:17:56 +00:00
|
|
|
// It will also lock resources while they are written to ensure only one
|
|
|
|
// write happens per time.
|
2015-03-23 18:02:12 +00:00
|
|
|
// The function call must return the number of bytes written.
|
2019-09-15 11:43:59 +00:00
|
|
|
WriteChunk(ctx context.Context, offset int64, src io.Reader) (int64, error)
|
2015-02-01 15:17:56 +00:00
|
|
|
// Read the fileinformation used to validate the offset and respond to HEAD
|
2015-11-19 14:18:26 +00:00
|
|
|
// requests. It may return an os.ErrNotExist which will be interpreted as a
|
2015-02-01 15:17:56 +00:00
|
|
|
// 404 Not Found.
|
2019-09-15 11:43:59 +00:00
|
|
|
GetInfo(ctx context.Context) (FileInfo, error)
|
2022-05-24 12:16:01 +00:00
|
|
|
// GetReader returns an io.ReadCloser which allows iterating of the content of an
|
2019-08-24 13:14:51 +00:00
|
|
|
// upload specified by its ID. It should attempt to provide a reader even if
|
|
|
|
// the upload has not been finished yet but it's not required.
|
|
|
|
// If the given upload could not be found, the error tusd.ErrNotFound should
|
|
|
|
// be returned.
|
2022-05-24 12:16:01 +00:00
|
|
|
GetReader(ctx context.Context) (io.ReadCloser, error)
|
2019-08-24 13:14:51 +00:00
|
|
|
// FinisherDataStore is the interface which can be implemented by DataStores
|
|
|
|
// which need to do additional operations once an entire upload has been
|
|
|
|
// completed. These tasks may include but are not limited to freeing unused
|
|
|
|
// resources or notifying other services. For example, S3Store uses this
|
|
|
|
// interface for removing a temporary object.
|
|
|
|
// FinishUpload executes additional operations for the finished upload which
|
|
|
|
// is specified by its ID.
|
2019-09-15 11:43:59 +00:00
|
|
|
FinishUpload(ctx context.Context) error
|
2015-12-26 23:44:02 +00:00
|
|
|
}
|
|
|
|
|
2019-08-24 13:14:51 +00:00
|
|
|
type DataStore interface {
|
|
|
|
// Create a new upload using the size as the file's length. The method must
|
|
|
|
// return an unique id which is used to identify the upload. If no backend
|
|
|
|
// (e.g. Riak) specifes the id you may want to use the uid package to
|
|
|
|
// generate one. The properties Size and MetaData will be filled.
|
2019-09-15 11:43:59 +00:00
|
|
|
NewUpload(ctx context.Context, info FileInfo) (upload Upload, err error)
|
2019-08-24 13:14:51 +00:00
|
|
|
|
2019-09-15 11:43:59 +00:00
|
|
|
GetUpload(ctx context.Context, id string) (upload Upload, err error)
|
2019-08-24 13:14:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type TerminatableUpload interface {
|
2015-02-28 13:47:39 +00:00
|
|
|
// Terminate an upload so any further requests to the resource, both reading
|
|
|
|
// and writing, must return os.ErrNotExist or similar.
|
2019-09-15 11:43:59 +00:00
|
|
|
Terminate(ctx context.Context) error
|
2015-02-01 13:57:57 +00:00
|
|
|
}
|
2015-12-08 21:08:54 +00:00
|
|
|
|
2019-08-24 13:14:51 +00:00
|
|
|
// TerminaterDataStore is the interface which must be implemented by DataStores
|
|
|
|
// if they want to receive DELETE requests using the Handler. If this interface
|
|
|
|
// is not implemented, no request handler for this method is attached.
|
|
|
|
type TerminaterDataStore interface {
|
|
|
|
AsTerminatableUpload(upload Upload) TerminatableUpload
|
2015-12-08 21:08:54 +00:00
|
|
|
}
|
2015-12-26 20:23:09 +00:00
|
|
|
|
2016-01-20 14:33:17 +00:00
|
|
|
// ConcaterDataStore is the interface required to be implemented if the
|
|
|
|
// Concatenation extension should be enabled. Only in this case, the handler
|
|
|
|
// will parse and respect the Upload-Concat header.
|
|
|
|
type ConcaterDataStore interface {
|
2019-09-19 10:14:25 +00:00
|
|
|
AsConcatableUpload(upload Upload) ConcatableUpload
|
|
|
|
}
|
|
|
|
|
|
|
|
type ConcatableUpload interface {
|
|
|
|
// ConcatUploads concatenates the content from the provided partial uploads
|
|
|
|
// and writes the result in the destination upload.
|
|
|
|
// The caller (usually the handler) must and will ensure that this
|
2016-01-20 14:33:17 +00:00
|
|
|
// destination upload has been created before with enough space to hold all
|
|
|
|
// partial uploads. The order, in which the partial uploads are supplied,
|
|
|
|
// must be respected during concatenation.
|
2019-09-19 10:14:25 +00:00
|
|
|
ConcatUploads(ctx context.Context, partialUploads []Upload) error
|
2016-01-20 14:33:17 +00:00
|
|
|
}
|
2018-05-05 15:37:56 +00:00
|
|
|
|
|
|
|
// LengthDeferrerDataStore is the interface that must be implemented if the
|
|
|
|
// creation-defer-length extension should be enabled. The extension enables a
|
|
|
|
// client to upload files when their total size is not yet known. Instead, the
|
|
|
|
// client must send the total size as soon as it becomes known.
|
|
|
|
type LengthDeferrerDataStore interface {
|
2019-08-24 13:14:51 +00:00
|
|
|
AsLengthDeclarableUpload(upload Upload) LengthDeclarableUpload
|
|
|
|
}
|
|
|
|
|
|
|
|
type LengthDeclarableUpload interface {
|
2019-09-15 11:43:59 +00:00
|
|
|
DeclareLength(ctx context.Context, length int64) error
|
2018-05-05 15:37:56 +00:00
|
|
|
}
|
2019-09-10 14:19:49 +00:00
|
|
|
|
|
|
|
// Locker is the interface required for custom lock persisting mechanisms.
|
|
|
|
// Common ways to store this information is in memory, on disk or using an
|
2019-09-12 10:37:43 +00:00
|
|
|
// external service, such as Redis.
|
2019-09-10 14:19:49 +00:00
|
|
|
// When multiple processes are attempting to access an upload, whether it be
|
|
|
|
// by reading or writing, a synchronization mechanism is required to prevent
|
|
|
|
// data corruption, especially to ensure correct offset values and the proper
|
|
|
|
// order of chunks inside a single upload.
|
|
|
|
type Locker interface {
|
|
|
|
// NewLock creates a new unlocked lock object for the given upload ID.
|
|
|
|
NewLock(id string) (Lock, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lock is the interface for a lock as returned from a Locker.
|
|
|
|
type Lock interface {
|
|
|
|
// Lock attempts to obtain an exclusive lock for the upload specified
|
|
|
|
// by its id.
|
2022-03-19 22:21:17 +00:00
|
|
|
// If the lock can be acquired, it will return without error. The requestUnlock
|
|
|
|
// callback is invoked when another caller attempts to create a lock. In this
|
|
|
|
// case, the holder of the lock should attempt to release the lock as soon
|
|
|
|
// as possible
|
|
|
|
// If the lock is already held, the holder's requestUnlock function will be
|
|
|
|
// invoked to request the lock to be released. If the context is cancelled before
|
|
|
|
// the lock can be acquired, ErrLockTimeout will be returned without acquiring
|
|
|
|
// the lock.
|
|
|
|
Lock(ctx context.Context, requestUnlock func()) error
|
2019-09-10 14:19:49 +00:00
|
|
|
// Unlock releases an existing lock for the given upload.
|
|
|
|
Unlock() error
|
|
|
|
}
|