add documentation
This commit is contained in:
parent
f628342b82
commit
f513d02938
17
datastore.go
17
datastore.go
|
@ -8,13 +8,28 @@ type MetaData map[string]string
|
||||||
|
|
||||||
type FileInfo struct {
|
type FileInfo struct {
|
||||||
Id string
|
Id string
|
||||||
|
// Total file size in bytes specified in the NewUpload call
|
||||||
Size int64
|
Size int64
|
||||||
|
// Offset in bytes (zero-based)
|
||||||
Offset int64
|
Offset int64
|
||||||
MetaData MetaData
|
MetaData MetaData
|
||||||
}
|
}
|
||||||
|
|
||||||
type DataStore interface {
|
type DataStore interface {
|
||||||
NewUpload(size int64, metaData MetaData) (string, error)
|
// 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.
|
||||||
|
NewUpload(size int64, metaData MetaData) (id string, err error)
|
||||||
|
// 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
|
||||||
|
// return an os.ErrNotExist which will be interpretet as a 404 Not Found.
|
||||||
|
// It will also lock resources while they are written to ensure only one
|
||||||
|
// write happens per time.
|
||||||
WriteChunk(id string, offset int64, src io.Reader) error
|
WriteChunk(id string, offset int64, src io.Reader) error
|
||||||
|
// Read the fileinformation used to validate the offset and respond to HEAD
|
||||||
|
// requests. It may return an os.ErrNotExist which will be interpretet as a
|
||||||
|
// 404 Not Found.
|
||||||
GetInfo(id string) (FileInfo, error)
|
GetInfo(id string) (FileInfo, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
// FileStore is a storage backend used as a tusd.DataStore in tusd.NewHandler.
|
||||||
|
// It stores the uploads in a directory specified in two different files: The
|
||||||
|
// `[id].info` files are used to store the fileinfo in JSON format. The
|
||||||
|
// `[id].bin` files contain the raw binary data uploaded.
|
||||||
|
// No cleanup is performed so you may want to run a cronjob to ensure your disk
|
||||||
|
// is not filled up with old and finished uploads.
|
||||||
package filestore
|
package filestore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -12,7 +18,11 @@ import (
|
||||||
|
|
||||||
var defaultFilePerm = os.FileMode(0666)
|
var defaultFilePerm = os.FileMode(0666)
|
||||||
|
|
||||||
|
// See the tusd.DataStore interface for documentation about the different
|
||||||
|
// methods.
|
||||||
type FileStore struct {
|
type FileStore struct {
|
||||||
|
// Relative or absolute path to store files in. FileStore does not check
|
||||||
|
// whether the path exists, you os.MkdirAll in this case on your own.
|
||||||
Path string
|
Path string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,14 +73,17 @@ func (store FileStore) GetInfo(id string) (tusd.FileInfo, error) {
|
||||||
return info, err
|
return info, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return the path to the .bin storing the binary data
|
||||||
func (store FileStore) binPath(id string) string {
|
func (store FileStore) binPath(id string) string {
|
||||||
return store.Path + "/" + id + ".bin"
|
return store.Path + "/" + id + ".bin"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return the path to the .info file storing the file's info
|
||||||
func (store FileStore) infoPath(id string) string {
|
func (store FileStore) infoPath(id string) string {
|
||||||
return store.Path + "/" + id + ".info"
|
return store.Path + "/" + id + ".info"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the entire information. Everything will be overwritten.
|
||||||
func (store FileStore) writeInfo(id string, info tusd.FileInfo) error {
|
func (store FileStore) writeInfo(id string, info tusd.FileInfo) error {
|
||||||
data, err := json.Marshal(info)
|
data, err := json.Marshal(info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -79,6 +92,7 @@ func (store FileStore) writeInfo(id string, info tusd.FileInfo) error {
|
||||||
return ioutil.WriteFile(store.infoPath(id), data, defaultFilePerm)
|
return ioutil.WriteFile(store.infoPath(id), data, defaultFilePerm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the .info file using the new upload.
|
||||||
func (store FileStore) setOffset(id string, offset int64) error {
|
func (store FileStore) setOffset(id string, offset int64) error {
|
||||||
info, err := store.GetInfo(id)
|
info, err := store.GetInfo(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
14
handler.go
14
handler.go
|
@ -25,6 +25,7 @@ var (
|
||||||
ErrSizeExceeded = errors.New("resource's size exceeded")
|
ErrSizeExceeded = errors.New("resource's size exceeded")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// HTTP status codes sent in the response when the specific error is returned.
|
||||||
var ErrStatusCodes = map[error]int{
|
var ErrStatusCodes = map[error]int{
|
||||||
ErrUnsupportedVersion: http.StatusPreconditionFailed,
|
ErrUnsupportedVersion: http.StatusPreconditionFailed,
|
||||||
ErrMaxSizeExceeded: http.StatusRequestEntityTooLarge,
|
ErrMaxSizeExceeded: http.StatusRequestEntityTooLarge,
|
||||||
|
@ -37,6 +38,8 @@ var ErrStatusCodes = map[error]int{
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
// DataStore implementation used to store and retrieve the single uploads.
|
||||||
|
// Must no be nil.
|
||||||
DataStore DataStore
|
DataStore DataStore
|
||||||
// MaxSize defines how many bytes may be stored in one single upload. If its
|
// MaxSize defines how many bytes may be stored in one single upload. If its
|
||||||
// value is is 0 or smaller no limit will be enforced.
|
// value is is 0 or smaller no limit will be enforced.
|
||||||
|
@ -56,6 +59,7 @@ type Handler struct {
|
||||||
locks map[string]bool
|
locks map[string]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a new handler using the given configuration.
|
||||||
func NewHandler(config Config) (*Handler, error) {
|
func NewHandler(config Config) (*Handler, error) {
|
||||||
base := config.BasePath
|
base := config.BasePath
|
||||||
uri, err := url.Parse(base)
|
uri, err := url.Parse(base)
|
||||||
|
@ -91,6 +95,7 @@ func NewHandler(config Config) (*Handler, error) {
|
||||||
return handler, nil
|
return handler, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implement the http.Handler interface.
|
||||||
func (handler *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (handler *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
go logger.Println(r.Method, r.URL.Path)
|
go logger.Println(r.Method, r.URL.Path)
|
||||||
|
|
||||||
|
@ -138,6 +143,8 @@ func (handler *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
handler.routeHandler.ServeHTTP(w, r)
|
handler.routeHandler.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a new file upload using the datastore after validating the length
|
||||||
|
// and parsing the metadata.
|
||||||
func (handler *Handler) postFile(w http.ResponseWriter, r *http.Request) {
|
func (handler *Handler) postFile(w http.ResponseWriter, r *http.Request) {
|
||||||
size, err := strconv.ParseInt(r.Header.Get("Entity-Length"), 10, 64)
|
size, err := strconv.ParseInt(r.Header.Get("Entity-Length"), 10, 64)
|
||||||
if err != nil || size < 0 {
|
if err != nil || size < 0 {
|
||||||
|
@ -165,6 +172,7 @@ func (handler *Handler) postFile(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the length and offset for the HEAD request
|
||||||
func (handler *Handler) headFile(w http.ResponseWriter, r *http.Request) {
|
func (handler *Handler) headFile(w http.ResponseWriter, r *http.Request) {
|
||||||
id := r.URL.Query().Get(":id")
|
id := r.URL.Query().Get(":id")
|
||||||
info, err := handler.dataStore.GetInfo(id)
|
info, err := handler.dataStore.GetInfo(id)
|
||||||
|
@ -182,6 +190,8 @@ func (handler *Handler) headFile(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusNoContent)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add a chunk to an upload. Only allowed if the upload is not locked and enough
|
||||||
|
// space is left.
|
||||||
func (handler *Handler) patchFile(w http.ResponseWriter, r *http.Request) {
|
func (handler *Handler) patchFile(w http.ResponseWriter, r *http.Request) {
|
||||||
id := r.URL.Query().Get(":id")
|
id := r.URL.Query().Get(":id")
|
||||||
|
|
||||||
|
@ -246,6 +256,8 @@ func (handler *Handler) patchFile(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusNoContent)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send the error in the response body. The status code will be looked up in
|
||||||
|
// ErrStatusCodes. If none is found 500 Internal Error will be used.
|
||||||
func (handler *Handler) sendError(w http.ResponseWriter, err error) {
|
func (handler *Handler) sendError(w http.ResponseWriter, err error) {
|
||||||
status, ok := ErrStatusCodes[err]
|
status, ok := ErrStatusCodes[err]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -256,6 +268,8 @@ func (handler *Handler) sendError(w http.ResponseWriter, err error) {
|
||||||
w.Write([]byte(err.Error() + "\n"))
|
w.Write([]byte(err.Error() + "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make an absolute URLs to the given upload id. If the base path is absolute
|
||||||
|
// it will be prepended else the host and protocol from the request is used.
|
||||||
func (handler *Handler) absFileUrl(r *http.Request, id string) string {
|
func (handler *Handler) absFileUrl(r *http.Request, id string) string {
|
||||||
if handler.isBasePathAbs {
|
if handler.isBasePathAbs {
|
||||||
return handler.basePath + id
|
return handler.basePath + id
|
||||||
|
|
Loading…
Reference in New Issue