add support for Termination extension
This commit is contained in:
parent
09056611d6
commit
9f94d0591b
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
33
handler.go
33
handler.go
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
Loading…
Reference in New Issue