From c470fe3a9dfbc40b74aed9518138993df2fc3240 Mon Sep 17 00:00:00 2001 From: Marius Date: Thu, 29 Mar 2018 14:40:43 +0200 Subject: [PATCH] Emit post-finish event for empty uploads See https://github.com/tus/tus-js-client/issues/106 --- patch_test.go | 1 + post_test.go | 36 ++++++++++++++++++++++++++++++++++++ unrouted_handler.go | 29 ++++++++++++++++++++++------- 3 files changed, 59 insertions(+), 7 deletions(-) diff --git a/patch_test.go b/patch_test.go index 3ccb430..c62d4b1 100644 --- a/patch_test.go +++ b/patch_test.go @@ -223,6 +223,7 @@ func TestPatch(t *testing.T) { gomock.InOrder( store.EXPECT().GetInfo("yes").Return(FileInfo{ + ID: "yes", Offset: 5, Size: 20, }, nil), diff --git a/post_test.go b/post_test.go index b7ffcc7..639308d 100644 --- a/post_test.go +++ b/post_test.go @@ -52,6 +52,42 @@ func TestPost(t *testing.T) { a.Equal(int64(300), info.Size) }) + SubTest(t, "CreateEmptyUpload", func(t *testing.T, store *MockFullDataStore) { + store.EXPECT().NewUpload(FileInfo{ + Size: 0, + MetaData: map[string]string{}, + }).Return("foo", nil) + + store.EXPECT().FinishUpload("foo").Return(nil) + + handler, _ := NewHandler(Config{ + DataStore: store, + BasePath: "https://buy.art/files/", + NotifyCompleteUploads: true, + }) + + handler.CompleteUploads = make(chan FileInfo, 1) + + (&httpTest{ + Method: "POST", + ReqHeader: map[string]string{ + "Tus-Resumable": "1.0.0", + "Upload-Length": "0", + }, + Code: http.StatusCreated, + ResHeader: map[string]string{ + "Location": "https://buy.art/files/foo", + }, + }).Run(handler, t) + + info := <-handler.CompleteUploads + + a := assert.New(t) + a.Equal("foo", info.ID) + a.Equal(int64(0), info.Size) + a.Equal(int64(0), info.Offset) + }) + SubTest(t, "CreateExceedingMaxSizeFail", func(t *testing.T, store *MockFullDataStore) { handler, _ := NewHandler(Config{ MaxSize: 400, diff --git a/unrouted_handler.go b/unrouted_handler.go index dde4eb3..38ad05e 100644 --- a/unrouted_handler.go +++ b/unrouted_handler.go @@ -284,6 +284,8 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) return } + info.ID = id + // Add the Location header directly after creating the new resource to even // include it in cases of failure when an error is returned url := handler.absFileURL(r, id) @@ -293,7 +295,6 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) handler.log("UploadCreated", "id", id, "size", i64toa(size), "url", url) if handler.config.NotifyCreatedUploads { - info.ID = id handler.CreatedUploads <- info } @@ -305,7 +306,6 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) info.Offset = size if handler.config.NotifyCompleteUploads { - info.ID = id handler.CompleteUploads <- info } } @@ -325,6 +325,11 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) handler.sendError(w, r, err) return } + } else if size == 0 { + // Directly finish the upload if the upload is empty (i.e. has a size of 0). + // This statement is in an else-if block to avoid causing duplicate calls + // to finishUploadIfComplete if an upload is empty and contains a chunk. + handler.finishUploadIfComplete(info) } handler.sendResp(w, r, http.StatusCreated) @@ -381,7 +386,8 @@ func (handler *UnroutedHandler) HeadFile(w http.ResponseWriter, r *http.Request) handler.sendResp(w, r, http.StatusOK) } -// PatchFile adds a chunk to an upload. Only allowed enough space is left. +// PatchFile adds a chunk to an upload. This operation is only allowed +// if enough space in the upload is left. func (handler *UnroutedHandler) PatchFile(w http.ResponseWriter, r *http.Request) { // Check for presence of application/offset+octet-stream @@ -445,7 +451,9 @@ func (handler *UnroutedHandler) PatchFile(w http.ResponseWriter, r *http.Request handler.sendResp(w, r, http.StatusNoContent) } -// PatchFile adds a chunk to an upload. Only allowed enough space is left. +// writeChunk reads the body from the requests r and appends it to the upload +// with the corresponding id. Afterwards, it will set the necessary response +// headers but will not send the response. func (handler *UnroutedHandler) writeChunk(id string, info FileInfo, w http.ResponseWriter, r *http.Request) error { // Get Content-Length if possible length := r.ContentLength @@ -489,19 +497,26 @@ func (handler *UnroutedHandler) writeChunk(id string, info FileInfo, w http.Resp newOffset := offset + bytesWritten w.Header().Set("Upload-Offset", strconv.FormatInt(newOffset, 10)) go handler.Metrics.incBytesReceived(uint64(bytesWritten)) + info.Offset = newOffset + return handler.finishUploadIfComplete(info) +} + +// finishUploadIfComplete checks whether an upload is completed (i.e. upload offset +// matches upload size) and if so, it will call the data store's FinishUpload +// function and send the necessary message on the CompleteUpload channel. +func (handler *UnroutedHandler) finishUploadIfComplete(info FileInfo) error { // If the upload is completed, ... - if newOffset == info.Size { + if info.Offset == info.Size { // ... allow custom mechanism to finish and cleanup the upload if handler.composer.UsesFinisher { - if err := handler.composer.Finisher.FinishUpload(id); err != nil { + if err := handler.composer.Finisher.FinishUpload(info.ID); err != nil { return err } } // ... send the info out to the channel if handler.config.NotifyCompleteUploads { - info.Offset = newOffset handler.CompleteUploads <- info }