diff --git a/datastore.go b/datastore.go index 03f79f0..956d1bf 100644 --- a/datastore.go +++ b/datastore.go @@ -8,13 +8,28 @@ type MetaData map[string]string type FileInfo struct { Id string + // Total file size in bytes specified in the NewUpload call Size int64 + // Offset in bytes (zero-based) Offset int64 MetaData MetaData } 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 + // 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) } diff --git a/filestore/filestore.go b/filestore/filestore.go index 898a35c..81414fa 100644 --- a/filestore/filestore.go +++ b/filestore/filestore.go @@ -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 import ( @@ -12,7 +18,11 @@ import ( var defaultFilePerm = os.FileMode(0666) +// See the tusd.DataStore interface for documentation about the different +// methods. 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 } @@ -63,14 +73,17 @@ func (store FileStore) GetInfo(id string) (tusd.FileInfo, error) { return info, err } +// Return the path to the .bin storing the binary data func (store FileStore) binPath(id string) string { return store.Path + "/" + id + ".bin" } +// Return the path to the .info file storing the file's info func (store FileStore) infoPath(id string) string { return store.Path + "/" + id + ".info" } +// Update the entire information. Everything will be overwritten. func (store FileStore) writeInfo(id string, info tusd.FileInfo) error { data, err := json.Marshal(info) 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) } +// Update the .info file using the new upload. func (store FileStore) setOffset(id string, offset int64) error { info, err := store.GetInfo(id) if err != nil { diff --git a/handler.go b/handler.go index e5c77cc..705d37e 100644 --- a/handler.go +++ b/handler.go @@ -25,6 +25,7 @@ var ( 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{ ErrUnsupportedVersion: http.StatusPreconditionFailed, ErrMaxSizeExceeded: http.StatusRequestEntityTooLarge, @@ -37,6 +38,8 @@ var ErrStatusCodes = map[error]int{ } type Config struct { + // DataStore implementation used to store and retrieve the single uploads. + // Must no be nil. DataStore DataStore // 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. @@ -56,6 +59,7 @@ type Handler struct { locks map[string]bool } +// Create a new handler using the given configuration. func NewHandler(config Config) (*Handler, error) { base := config.BasePath uri, err := url.Parse(base) @@ -91,6 +95,7 @@ func NewHandler(config Config) (*Handler, error) { return handler, nil } +// Implement the http.Handler interface. func (handler *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 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) } +// 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) { size, err := strconv.ParseInt(r.Header.Get("Entity-Length"), 10, 64) if err != nil || size < 0 { @@ -165,6 +172,7 @@ func (handler *Handler) postFile(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusCreated) } +// Returns the length and offset for the HEAD request func (handler *Handler) headFile(w http.ResponseWriter, r *http.Request) { id := r.URL.Query().Get(":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) } +// 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) { id := r.URL.Query().Get(":id") @@ -246,6 +256,8 @@ func (handler *Handler) patchFile(w http.ResponseWriter, r *http.Request) { 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) { status, ok := ErrStatusCodes[err] if !ok { @@ -256,6 +268,8 @@ func (handler *Handler) sendError(w http.ResponseWriter, err error) { 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 { if handler.isBasePathAbs { return handler.basePath + id