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)
|
||||
}
|
||||
|
||||
var store tusd.DataStore
|
||||
var store tusd.TerminaterDataStore
|
||||
store = filestore.New(dir)
|
||||
|
||||
if storeSize > 0 {
|
||||
|
|
|
@ -51,6 +51,14 @@ type DataStore interface {
|
|||
// If the returned reader also implements the io.Closer interface, the
|
||||
// Close() method will be invoked once everything has been read.
|
||||
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
|
||||
// and writing, must return os.ErrNotExist or similar.
|
||||
Terminate(id string) error
|
||||
|
|
|
@ -39,9 +39,13 @@ func NewHandler(config Config) (*Handler, error) {
|
|||
mux.Post("", http.HandlerFunc(handler.PostFile))
|
||||
mux.Head(":id", http.HandlerFunc(handler.HeadFile))
|
||||
mux.Get(":id", http.HandlerFunc(handler.GetFile))
|
||||
mux.Del(":id", http.HandlerFunc(handler.DelFile))
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -28,10 +28,6 @@ func (store zeroStore) GetReader(id string) (io.Reader, error) {
|
|||
return nil, ErrNotImplemented
|
||||
}
|
||||
|
||||
func (store zeroStore) Terminate(id string) error {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
|
||||
type httpTest struct {
|
||||
Name string
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ import (
|
|||
|
||||
type LimitedStore struct {
|
||||
StoreSize int64
|
||||
tusd.DataStore
|
||||
tusd.TerminaterDataStore
|
||||
|
||||
uploads map[string]int64
|
||||
usedSize int64
|
||||
|
@ -42,11 +42,13 @@ 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) 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
|
||||
func New(storeSize int64, dataStore tusd.DataStore) *LimitedStore {
|
||||
// New creates a new limited store with the given size as the maximum storage
|
||||
// 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{
|
||||
StoreSize: storeSize,
|
||||
DataStore: dataStore,
|
||||
TerminaterDataStore: dataStore,
|
||||
uploads: make(map[string]int64),
|
||||
mutex: new(sync.Mutex),
|
||||
}
|
||||
|
@ -60,7 +62,7 @@ func (store *LimitedStore) NewUpload(info tusd.FileInfo) (string, error) {
|
|||
return "", err
|
||||
}
|
||||
|
||||
id, err := store.DataStore.NewUpload(info)
|
||||
id, err := store.TerminaterDataStore.NewUpload(info)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -79,7 +81,7 @@ 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 {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -24,10 +24,6 @@ func (store zeroStore) GetReader(id string) (io.Reader, error) {
|
|||
return nil, tusd.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (store zeroStore) Terminate(id string) error {
|
||||
return tusd.ErrNotImplemented
|
||||
}
|
||||
|
||||
func TestMemoryLocker(t *testing.T) {
|
||||
var locker tusd.LockerDataStore
|
||||
locker = NewMemoryLocker(&zeroStore{})
|
||||
|
|
|
@ -17,7 +17,7 @@ func TestOptions(t *testing.T) {
|
|||
Method: "OPTIONS",
|
||||
Code: http.StatusNoContent,
|
||||
ResHeader: map[string]string{
|
||||
"Tus-Extension": "creation,concatenation,termination",
|
||||
"Tus-Extension": "creation,concatenation",
|
||||
"Tus-Version": "1.0.0",
|
||||
"Tus-Resumable": "1.0.0",
|
||||
"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{
|
||||
Name: "Successful request",
|
||||
Method: "DELETE",
|
||||
|
@ -36,3 +46,19 @@ func TestTerminate(t *testing.T) {
|
|||
Code: http.StatusNoContent,
|
||||
}).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
|
||||
basePath string
|
||||
logger *log.Logger
|
||||
extensions string
|
||||
|
||||
// For each finished upload the corresponding info object will be sent using
|
||||
// this unbuffered channel. The NotifyCompleteUploads property in the Config
|
||||
|
@ -108,6 +109,12 @@ func NewUnroutedHandler(config Config) (*UnroutedHandler, error) {
|
|||
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{
|
||||
config: config,
|
||||
dataStore: config.DataStore,
|
||||
|
@ -115,6 +122,7 @@ func NewUnroutedHandler(config Config) (*UnroutedHandler, error) {
|
|||
isBasePathAbs: uri.IsAbs(),
|
||||
CompleteUploads: make(chan FileInfo),
|
||||
logger: logger,
|
||||
extensions: extensions,
|
||||
}
|
||||
|
||||
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-Extension", "creation,concatenation,termination")
|
||||
header.Set("Tus-Extension", handler.extensions)
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
|
@ -429,6 +437,13 @@ func (handler *UnroutedHandler) GetFile(w http.ResponseWriter, r *http.Request)
|
|||
|
||||
// DelFile terminates an upload permanently.
|
||||
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)
|
||||
if err != nil {
|
||||
handler.sendError(w, r, err)
|
||||
|
@ -444,7 +459,7 @@ func (handler *UnroutedHandler) DelFile(w http.ResponseWriter, r *http.Request)
|
|||
defer locker.UnlockUpload(id)
|
||||
}
|
||||
|
||||
err = handler.dataStore.Terminate(id)
|
||||
err = tstore.Terminate(id)
|
||||
if err != nil {
|
||||
handler.sendError(w, r, err)
|
||||
return
|
||||
|
|
Loading…
Reference in New Issue