add support for Termination extension

This commit is contained in:
Acconut 2015-02-28 14:47:39 +01:00
parent 09056611d6
commit 9f94d0591b
7 changed files with 97 additions and 2 deletions

View File

@ -48,4 +48,7 @@ type DataStore interface {
// tusd.ErrNotImplemented. The length of the resource is determined by // tusd.ErrNotImplemented. The length of the resource is determined by
// retrieving the offset using GetInfo. // retrieving the offset using GetInfo.
GetReader(id string) (io.Reader, error) GetReader(id string) (io.Reader, error)
// Terminate an upload so any further requests to the resource, both reading
// and writing, must return os.ErrNotExist or similar.
Terminate(id string) error
} }

View File

@ -72,6 +72,16 @@ func (store FileStore) GetReader(id string) (io.Reader, error) {
return os.Open(store.binPath(id)) return os.Open(store.binPath(id))
} }
func (store FileStore) Terminate(id string) error {
if err := os.Remove(store.infoPath(id)); err != nil {
return err
}
if err := os.Remove(store.binPath(id)); err != nil {
return err
}
return nil
}
// Return the path to the .bin storing the binary data // 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"

View File

@ -2,6 +2,7 @@ package filestore
import ( import (
"io/ioutil" "io/ioutil"
"os"
"strings" "strings"
"testing" "testing"
@ -75,4 +76,14 @@ func TestFilestore(t *testing.T) {
if string(content) != "hello world" { if string(content) != "hello world" {
t.Errorf("expected content to be 'hello world'") t.Errorf("expected content to be 'hello world'")
} }
// Terminate upload
if err := store.Terminate(id); err != nil {
t.Fatal(err)
}
// Test if upload is deleted
if _, err := store.GetInfo(id); !os.IsNotExist(err) {
t.Fatal("expected os.ErrIsNotExist")
}
} }

View File

@ -104,6 +104,7 @@ func NewHandler(config Config) (*Handler, error) {
mux.Post("", http.HandlerFunc(handler.postFile)) mux.Post("", http.HandlerFunc(handler.postFile))
mux.Head(":id", http.HandlerFunc(handler.headFile)) mux.Head(":id", http.HandlerFunc(handler.headFile))
mux.Get(":id", http.HandlerFunc(handler.getFile)) mux.Get(":id", http.HandlerFunc(handler.getFile))
mux.Del(":id", http.HandlerFunc(handler.delFile))
mux.Add("PATCH", ":id", http.HandlerFunc(handler.patchFile)) mux.Add("PATCH", ":id", http.HandlerFunc(handler.patchFile))
return handler, nil return handler, nil
@ -141,7 +142,7 @@ func (handler *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
header.Set("TUS-Version", "1.0.0") header.Set("TUS-Version", "1.0.0")
header.Set("TUS-Extension", "file-creation,metadata,concatenation") header.Set("TUS-Extension", "file-creation,metadata,concatenation,termination")
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
return return
@ -370,6 +371,36 @@ func (handler *Handler) getFile(w http.ResponseWriter, r *http.Request) {
io.Copy(w, src) io.Copy(w, src)
} }
// Terminate an upload permanently.
func (handler *Handler) delFile(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get(":id")
// Ensure file is not locked
if _, ok := handler.locks[id]; ok {
handler.sendError(w, ErrFileLocked)
return
}
// Lock file for further writes (heads are allowed)
handler.locks[id] = true
// File will be unlocked regardless of an error or success
defer func() {
delete(handler.locks, id)
}()
err := handler.dataStore.Terminate(id)
if err != nil {
if os.IsNotExist(err) {
err = ErrNotFound
}
handler.sendError(w, err)
return
}
w.WriteHeader(http.StatusNoContent)
}
// Send the error in the response body. The status code will be looked up in // 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. // 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) {

View File

@ -24,6 +24,10 @@ func (store zeroStore) GetReader(id string) (io.Reader, error) {
return nil, ErrNotImplemented return nil, ErrNotImplemented
} }
func (store zeroStore) Terminate(id string) error {
return ErrNotImplemented
}
type httpTest struct { type httpTest struct {
Name string Name string

View File

@ -15,7 +15,7 @@ func TestOptions(t *testing.T) {
Method: "OPTIONS", Method: "OPTIONS",
Code: http.StatusNoContent, Code: http.StatusNoContent,
ResHeader: map[string]string{ ResHeader: map[string]string{
"TUS-Extension": "file-creation,metadata,concatenation", "TUS-Extension": "file-creation,metadata,concatenation,termination",
"TUS-Version": "1.0.0", "TUS-Version": "1.0.0",
"TUS-Resumable": "1.0.0", "TUS-Resumable": "1.0.0",
"TUS-Max-Size": "400", "TUS-Max-Size": "400",

36
terminate_test.go Normal file
View File

@ -0,0 +1,36 @@
package tusd
import (
"net/http"
"testing"
)
type terminateStore struct {
t *testing.T
zeroStore
}
func (s terminateStore) Terminate(id string) error {
if id != "foo" {
s.t.Fatal("unexpected id")
}
return nil
}
func TestTerminate(t *testing.T) {
handler, _ := NewHandler(Config{
DataStore: terminateStore{
t: t,
},
})
(&httpTest{
Name: "Successful request",
Method: "DELETE",
URL: "foo",
ReqHeader: map[string]string{
"TUS-Resumable": "1.0.0",
},
Code: http.StatusNoContent,
}).Run(handler, t)
}