Move terminating method in own interface
In addition, the DELETE handler is only provided if the TerminaterDataStore interface is implemented.
This commit is contained in:
parent
f96e2614fc
commit
4af7434c10
|
@ -43,7 +43,7 @@ func main() {
|
||||||
stderr.Fatalf("Unable to ensure directory exists: %s", err)
|
stderr.Fatalf("Unable to ensure directory exists: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var store tusd.DataStore
|
var store tusd.TerminaterDataStore
|
||||||
store = filestore.New(dir)
|
store = filestore.New(dir)
|
||||||
|
|
||||||
if storeSize > 0 {
|
if storeSize > 0 {
|
||||||
|
|
|
@ -51,6 +51,14 @@ type DataStore interface {
|
||||||
// If the returned reader also implements the io.Closer interface, the
|
// If the returned reader also implements the io.Closer interface, the
|
||||||
// Close() method will be invoked once everything has been read.
|
// Close() method will be invoked once everything has been read.
|
||||||
GetReader(id string) (io.Reader, error)
|
GetReader(id string) (io.Reader, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TerminaterDataStore is the interface which must be implemented by DataStores
|
||||||
|
// if they want to receive DELETE requests using the Handler. If this interface
|
||||||
|
// is not implemented, no request handler for this method is attached.
|
||||||
|
type TerminaterDataStore interface {
|
||||||
|
DataStore
|
||||||
|
|
||||||
// Terminate an upload so any further requests to the resource, both reading
|
// Terminate an upload so any further requests to the resource, both reading
|
||||||
// and writing, must return os.ErrNotExist or similar.
|
// and writing, must return os.ErrNotExist or similar.
|
||||||
Terminate(id string) error
|
Terminate(id string) error
|
||||||
|
|
|
@ -39,9 +39,13 @@ 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))
|
||||||
|
|
||||||
|
// Only attach the DELETE handler if the Terminate() method is provided
|
||||||
|
if _, ok := config.DataStore.(TerminaterDataStore); ok {
|
||||||
|
mux.Del(":id", http.HandlerFunc(handler.DelFile))
|
||||||
|
}
|
||||||
|
|
||||||
return routedHandler, nil
|
return routedHandler, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,10 +28,6 @@ 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
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ import (
|
||||||
|
|
||||||
type LimitedStore struct {
|
type LimitedStore struct {
|
||||||
StoreSize int64
|
StoreSize int64
|
||||||
tusd.DataStore
|
tusd.TerminaterDataStore
|
||||||
|
|
||||||
uploads map[string]int64
|
uploads map[string]int64
|
||||||
usedSize int64
|
usedSize int64
|
||||||
|
@ -42,13 +42,15 @@ func (p pairlist) Len() int { return len(p) }
|
||||||
func (p pairlist) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
func (p pairlist) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||||
func (p pairlist) Less(i, j int) bool { return p[i].value < p[j].value }
|
func (p pairlist) Less(i, j int) bool { return p[i].value < p[j].value }
|
||||||
|
|
||||||
// Create a new limited store with the given size as the maximum storage size
|
// New creates a new limited store with the given size as the maximum storage
|
||||||
func New(storeSize int64, dataStore tusd.DataStore) *LimitedStore {
|
// size. The wrapped data store needs to implement the TerminaterDataStore
|
||||||
|
// interface, in order to provide the required Terminate method.
|
||||||
|
func New(storeSize int64, dataStore tusd.TerminaterDataStore) *LimitedStore {
|
||||||
return &LimitedStore{
|
return &LimitedStore{
|
||||||
StoreSize: storeSize,
|
StoreSize: storeSize,
|
||||||
DataStore: dataStore,
|
TerminaterDataStore: dataStore,
|
||||||
uploads: make(map[string]int64),
|
uploads: make(map[string]int64),
|
||||||
mutex: new(sync.Mutex),
|
mutex: new(sync.Mutex),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +62,7 @@ func (store *LimitedStore) NewUpload(info tusd.FileInfo) (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := store.DataStore.NewUpload(info)
|
id, err := store.TerminaterDataStore.NewUpload(info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -79,7 +81,7 @@ func (store *LimitedStore) Terminate(id string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *LimitedStore) terminate(id string) error {
|
func (store *LimitedStore) terminate(id string) error {
|
||||||
err := store.DataStore.Terminate(id)
|
err := store.TerminaterDataStore.Terminate(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,10 +24,6 @@ func (store zeroStore) GetReader(id string) (io.Reader, error) {
|
||||||
return nil, tusd.ErrNotImplemented
|
return nil, tusd.ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store zeroStore) Terminate(id string) error {
|
|
||||||
return tusd.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMemoryLocker(t *testing.T) {
|
func TestMemoryLocker(t *testing.T) {
|
||||||
var locker tusd.LockerDataStore
|
var locker tusd.LockerDataStore
|
||||||
locker = NewMemoryLocker(&zeroStore{})
|
locker = NewMemoryLocker(&zeroStore{})
|
||||||
|
|
|
@ -17,7 +17,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": "creation,concatenation,termination",
|
"Tus-Extension": "creation,concatenation",
|
||||||
"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",
|
||||||
|
|
|
@ -26,6 +26,16 @@ func TestTerminate(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
(&httpTest{
|
||||||
|
Name: "Successful OPTIONS request",
|
||||||
|
Method: "OPTIONS",
|
||||||
|
URL: "",
|
||||||
|
ResHeader: map[string]string{
|
||||||
|
"Tus-Extension": "creation,concatenation,termination",
|
||||||
|
},
|
||||||
|
Code: http.StatusNoContent,
|
||||||
|
}).Run(handler, t)
|
||||||
|
|
||||||
(&httpTest{
|
(&httpTest{
|
||||||
Name: "Successful request",
|
Name: "Successful request",
|
||||||
Method: "DELETE",
|
Method: "DELETE",
|
||||||
|
@ -36,3 +46,19 @@ func TestTerminate(t *testing.T) {
|
||||||
Code: http.StatusNoContent,
|
Code: http.StatusNoContent,
|
||||||
}).Run(handler, t)
|
}).Run(handler, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTerminateNotImplemented(t *testing.T) {
|
||||||
|
handler, _ := NewHandler(Config{
|
||||||
|
DataStore: zeroStore{},
|
||||||
|
})
|
||||||
|
|
||||||
|
(&httpTest{
|
||||||
|
Name: "TerminaterDataStore not implemented",
|
||||||
|
Method: "DELETE",
|
||||||
|
URL: "foo",
|
||||||
|
ReqHeader: map[string]string{
|
||||||
|
"Tus-Resumable": "1.0.0",
|
||||||
|
},
|
||||||
|
Code: http.StatusMethodNotAllowed,
|
||||||
|
}).Run(handler, t)
|
||||||
|
}
|
||||||
|
|
|
@ -76,6 +76,7 @@ type UnroutedHandler struct {
|
||||||
isBasePathAbs bool
|
isBasePathAbs bool
|
||||||
basePath string
|
basePath string
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
|
extensions string
|
||||||
|
|
||||||
// For each finished upload the corresponding info object will be sent using
|
// For each finished upload the corresponding info object will be sent using
|
||||||
// this unbuffered channel. The NotifyCompleteUploads property in the Config
|
// this unbuffered channel. The NotifyCompleteUploads property in the Config
|
||||||
|
@ -108,6 +109,12 @@ func NewUnroutedHandler(config Config) (*UnroutedHandler, error) {
|
||||||
base = "/" + base
|
base = "/" + base
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only promote extesions using the Tus-Extension header which are implemented
|
||||||
|
extensions := "creation,concatenation"
|
||||||
|
if _, ok := config.DataStore.(TerminaterDataStore); ok {
|
||||||
|
extensions += ",termination"
|
||||||
|
}
|
||||||
|
|
||||||
handler := &UnroutedHandler{
|
handler := &UnroutedHandler{
|
||||||
config: config,
|
config: config,
|
||||||
dataStore: config.DataStore,
|
dataStore: config.DataStore,
|
||||||
|
@ -115,6 +122,7 @@ func NewUnroutedHandler(config Config) (*UnroutedHandler, error) {
|
||||||
isBasePathAbs: uri.IsAbs(),
|
isBasePathAbs: uri.IsAbs(),
|
||||||
CompleteUploads: make(chan FileInfo),
|
CompleteUploads: make(chan FileInfo),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
extensions: extensions,
|
||||||
}
|
}
|
||||||
|
|
||||||
return handler, nil
|
return handler, nil
|
||||||
|
@ -167,7 +175,7 @@ func (handler *UnroutedHandler) Middleware(h http.Handler) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
header.Set("Tus-Version", "1.0.0")
|
header.Set("Tus-Version", "1.0.0")
|
||||||
header.Set("Tus-Extension", "creation,concatenation,termination")
|
header.Set("Tus-Extension", handler.extensions)
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
return
|
return
|
||||||
|
@ -429,6 +437,13 @@ func (handler *UnroutedHandler) GetFile(w http.ResponseWriter, r *http.Request)
|
||||||
|
|
||||||
// DelFile terminates an upload permanently.
|
// DelFile terminates an upload permanently.
|
||||||
func (handler *UnroutedHandler) DelFile(w http.ResponseWriter, r *http.Request) {
|
func (handler *UnroutedHandler) DelFile(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Abort the request handling if the required interface is not implemented
|
||||||
|
tstore, ok := handler.config.DataStore.(TerminaterDataStore)
|
||||||
|
if !ok {
|
||||||
|
handler.sendError(w, r, ErrNotImplemented)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
id, err := extractIDFromPath(r.URL.Path)
|
id, err := extractIDFromPath(r.URL.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handler.sendError(w, r, err)
|
handler.sendError(w, r, err)
|
||||||
|
@ -444,7 +459,7 @@ func (handler *UnroutedHandler) DelFile(w http.ResponseWriter, r *http.Request)
|
||||||
defer locker.UnlockUpload(id)
|
defer locker.UnlockUpload(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handler.dataStore.Terminate(id)
|
err = tstore.Terminate(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handler.sendError(w, r, err)
|
handler.sendError(w, r, err)
|
||||||
return
|
return
|
||||||
|
|
Loading…
Reference in New Issue